OSDN Git Service

This commit was manufactured by cvs2svn to create branch 'Bb58'.
[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 }
7
8 uses
9         Windows, SysUtils, Classes, Math, DateUtils,
10         IdURI,
11         PlugInMain in 'PlugInMain.pas',
12         ThreadItem in 'ThreadItem.pas',
13         BoardItem in 'BoardItem.pas',
14         FilePath in 'FilePath.pas',
15     MojuUtils in '..\..\MojuUtils.pas';
16
17 {$R *.res}
18
19 type
20         // =========================================================================
21         // TMachiBBSThreadItem
22         // =========================================================================
23         TMachiBBSThreadItem = class(TThreadItem)
24         private
25                 FIsTemporary    : Boolean;
26                 FDat                                    : TStringList;
27                 //FFilePath             : String;
28         public
29                 constructor     Create( inInstance : DWORD );
30                 destructor      Destroy; override;
31
32         private
33                 function        Download : TDownloadState;
34                 function        Write( inName : string; inMail : string; inMessage : string ) : TDownloadState;
35                 function        GetRes( inNo : Integer ) : string;
36                 function        GetDat( inNo : Integer ) : string;
37                 function        GetHeader( inOptionalHeader : string ) : string;
38                 function        GetFooter( inOptionalFooter : string ) : string;
39                 function        GetBoardURL : string;
40
41                 procedure       To2chDat( ioHTML : TStringList; inStartNo : Integer = 1 );
42                 procedure       LoadDat;
43                 procedure       FreeDat;
44                 function        ReadURL : string;
45                 //property      FilePath : string read FFilePath;
46         end;
47
48         // =========================================================================
49         // TMachiBBSBoardItem
50         // =========================================================================
51         TMachiBBSBoardItem = class(TBoardItem)
52         private
53                 FIsTemporary    : Boolean;
54                 FDat                                    : TStringList;
55
56         public
57                 constructor     Create( inInstance : DWORD );
58                 destructor      Destroy; override;
59
60         private
61                 function        Download : TDownloadState;
62                 function        CreateThread( inSubject : string; inName : string; inMail : string; inMessage : string ) : TDownloadState;
63                 function        ToThreadURL( inFileName : string ) : string;
64                 procedure       EnumThread( inCallBack : TBoardItemEnumThreadCallBack );
65
66                 function        SubjectURL : string;
67         end;
68
69         // =========================================================================
70         // \83T\83u\83W\83F\83N\83g\83\8c\83R\81[\83h
71         // =========================================================================
72         TSubjectRec = record
73                 FFileName: string;
74                 FTitle: string;
75                 FCount: Integer;
76         end;
77
78 const
79         LOG_DIR                                         = 'MachiBBS\';
80         SUBJECT_NAME                    = 'subject.txt';
81
82         PLUGIN_NAME                             = 'MachiBBSPlugIn';
83         MAJOR_VERSION                   = 1;
84         MINOR_VERSION                   = 0;
85         RELEASE_VERSION         = 'beta';
86         REVISION_VERSION        = 19;
87
88 // =========================================================================
89 // \8eG\97p\8aÖ\90\94
90 // =========================================================================
91
92 // *************************************************************************
93 // \83e\83\93\83|\83\89\83\8a\82È\83p\83X\82Ì\8eæ\93¾
94 // *************************************************************************
95 function TemporaryFile : string;
96 var
97         tempPath : array [0..MAX_PATH] of       char;
98 begin
99
100         GetTempPath( SizeOf(tempPath), tempPath );
101         repeat
102                 Result := tempPath + IntToStr( Random( $7fffffff ) );
103         until not FileExists( Result );
104
105 end;
106
107 // *************************************************************************
108 // \82Ü\82¿BBS\97p\83\8d\83O\83t\83H\83\8b\83_\8eæ\93¾
109 // *************************************************************************
110 function MyLogFolder : string;
111 var
112         folder : PChar;
113 begin
114
115         folder := LogFolder;
116         if Length( folder ) = 0 then
117                 Result := ''
118         else
119                 Result := folder + LOG_DIR;
120     DisposeResultString(folder);
121
122 end;
123
124 (*************************************************************************
125  *\83f\83B\83\8c\83N\83g\83\8a\82ª\91\8dÝ\82·\82é\82©\83`\83F\83b\83N
126  *************************************************************************)
127 function DirectoryExistsEx(const Name: string): Boolean;
128 var
129         Code: Integer;
130 begin
131         Code := GetFileAttributes(PChar(Name));
132         Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
133 end;
134
135 (*************************************************************************
136  *\83f\83B\83\8c\83N\83g\83\8a\8dì\90¬\81i\95¡\90\94\8aK\91w\91Î\89\9e\81j
137  *************************************************************************)
138 function ForceDirectoriesEx(Dir: string): Boolean;
139 begin
140         Result := True;
141         if Length(Dir) = 0 then
142                 raise Exception.Create('\83t\83H\83\8b\83_\82ª\8dì\90¬\8fo\97\88\82Ü\82¹\82ñ');
143         Dir := ExcludeTrailingPathDelimiter(Dir);
144         if (Length(Dir) < 3) or DirectoryExistsEx(Dir)
145                 or (ExtractFilePath(Dir) = Dir) then Exit; // avoid 'xyz:\' problem.
146         Result := ForceDirectoriesEx(ExtractFilePath(Dir)) and CreateDir(Dir);
147 end;
148
149 // \82Æ\82è\82 \82¦\82¸\82Ì\91ã\97p\95i\82È\82Ì\82Å chrWhite \82ð\8dl\97\82µ\82Ä\82¢\82È\82¢\82±\82Æ\82É\92\8d\88Ó\81I\81I\81I
150 procedure ExtractHttpFields(
151         const chrSep : TSysCharSet;
152         const chrWhite : TSysCharSet;
153         const strValue : string;
154         var strResult : TStringList;
155         unknownFlag : boolean = false
156 );
157 var
158         last, p, strLen : Integer;
159 begin
160
161         strLen := Length( strValue );
162         p := 1;
163         last := 1;
164
165         while p <= strLen do
166         begin
167
168                 if strValue[ p ] in chrSep then
169                 begin
170                         strResult.Add( Copy( strValue, last, p - last ) );
171                         last := p + 1;
172                 end;
173
174                 p := p + 1;
175
176         end;
177
178         if last <> p then
179                 strResult.Add( Copy( strValue, last, strLen - last + 1 ) );
180
181 end;
182
183 \rfunction HttpEncode(
184 \r       const strValue : string
185 ) : string;
186 var
187         i : Integer;
188         strLen : Integer;
189         strResult : string;
190         b : Integer;
191 const
192         kHexCode : array [0..15] of char = (
193                                 '0', '1', '2', '3', '4', '5', '6', '7',
194                                 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' );
195 begin
196
197         strLen := Length( strValue );
198         i := 1;
199
200         while i <= strLen do
201         begin
202
203                 case strValue[ i ] of
204                 '0' .. '9', 'a' .. 'z', 'A' .. 'Z', '*', '-', '.', '@', '_':
205                         begin
206                                 strResult := strResult + strValue[ i ];
207                         end;
208                 else
209                         begin
210                                 b := Integer( strValue[ i ] );
211                                 strResult := strResult + '%'
212                                                                 + kHexCode[ b div $10 ]
213                                                                 + kHexCode[ b mod $10 ];
214                         end;
215                 end;
216
217                 i := i + 1;
218
219         end;
220
221         Result := strResult;
222
223 end;
224
225
226
227 // =========================================================================
228 // PlugIn
229 // =========================================================================
230
231 // *************************************************************************
232 // \83v\83\89\83O\83C\83\93\82Ì\83o\81[\83W\83\87\83\93\82ð\97v\8b\81\82³\82ê\82½
233 // *************************************************************************
234 procedure OnVersionInfo(
235         var outAgent            : PChar;        // \83o\81[\83W\83\87\83\93\82ð\88ê\90Ø\8aÜ\82Ü\82È\82¢\8f\83\90\88\82È\96¼\8fÌ
236         var outMajor            : DWORD;        // \83\81\83W\83\83\81[\83o\81[\83W\83\87\83\93
237         var outMinor            : DWORD;        // \83}\83C\83i\81[\83o\81[\83W\83\87\83\93
238         var outRelease  : PChar;        // \83\8a\83\8a\81[\83X\92i\8aK\96¼
239         var outRevision : DWORD         // \83\8a\83r\83W\83\87\83\93\83i\83\93\83o\81[
240 ); stdcall;
241 begin
242
243         try
244                 outAgent                := CreateResultString( PChar( PLUGIN_NAME ) );
245                 outMajor                := MAJOR_VERSION;
246                 outMinor                := MINOR_VERSION;
247                 outRelease      := CreateResultString( PChar( RELEASE_VERSION ) );
248                 outRevision     := REVISION_VERSION;
249         except
250                 outAgent                := nil;
251                 outMajor                := 0;
252                 outMinor                := 0;
253                 outRelease      := nil;
254                 outRevision     := 0;
255         end;
256
257 end;
258
259 // *************************************************************************
260 // \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©
261 // *************************************************************************
262 function OnAcceptURL(
263         inURL                   : PChar                         // \94»\92f\82ð\8bÂ\82¢\82Å\82¢\82é URL
264 ): TAcceptType; stdcall;        // URL \82Ì\8eí\97Þ
265 var
266         uri                             : TIdURI;
267         uriList         : TStringList;
268         foundPos        : Integer;
269 const
270         BBS_HOST                = 'machi.to';
271         THREAD_MARK     = '/bbs/read.pl';
272     THREAD_MARK2= '/bbs/read.cgi';
273 begin
274
275         try
276                 // \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é
277                 uri                     := TIdURI.Create( inURL );
278                 uriList := TStringList.Create;
279                 try
280                         ExtractHttpFields( ['/'], [], uri.Path, uriList );
281                         foundPos := AnsiPos( BBS_HOST, uri.Host );
282                         if (foundPos > 0) and (Length( uri.Host ) - foundPos + 1 = Length( BBS_HOST )) then begin
283                                 foundPos := Pos( THREAD_MARK, inURL );
284                 if (foundPos = 0) then begin
285                     // \90VURL\91Î\89\9e
286                     foundPos := Pos( THREAD_MARK2, inURL );
287                 end;
288                                 if foundPos > 0 then
289                                         Result := atThread
290                                 else if (uriList.Count > 1) and (uri.Path <> '/') then  // \8dÅ\8cã\82ª '/' \82Å\95Â\82ß\82ç\82ê\82Ä\82é\82È\82ç 3
291                                         Result := atBoard
292                                 else
293                                         Result := atBBS;
294                         end else begin
295                                 Result := atNoAccept;
296                         end;
297                 finally
298                         uri.Free;
299                         uriList.Free;
300                 end;
301         except
302                 Result := atNoAccept;
303         end;
304
305 end;
306
307 // *************************************************************************
308 // \8ew\92è\82µ\82½ URL \82ðBoard\82ÌURL\82É\95Ï\8a·
309 // *************************************************************************
310 procedure OnExtractBoardURL(
311         inURL   : PChar;
312         var outURL      : PChar
313 ); stdcall;
314 var
315         uri                     : TIdURI;
316         uriList         : TStringList;
317         URL         : String;
318 const
319         THREAD_MARK     = '/bbs/read.pl';
320     THREAD_MARK2= '/bbs/read.cgi';
321 begin
322         URL := string(inURL);
323         if AnsiPos(THREAD_MARK, URL) > 0 then begin
324                 if Copy( inURL, Length( inURL ), 1 ) = '/' then
325                         uri := TIdURI.Create( URL )
326                 else
327                         uri := TIdURI.Create( URL + '/' );
328
329                 uriList := TStringList.Create;
330                 try
331                         ExtractHttpFields(
332                                 ['&'], [],
333                                 Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ),uriList );
334                         // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
335                         // http://hokkaido.machi.to/hokkaidou/
336                         URL := uri.Protocol + '://' + uri.Host + '/' + uriList.Values[ 'BBS' ] + '/';
337                         outURL := CreateResultString(URL);
338                 finally
339                         uri.Free;
340                         uriList.Free;
341                 end;
342     end else if AnsiPos(THREAD_MARK2, URL) > 0 then begin
343                 if Copy( inURL, Length( inURL ), 1 ) = '/' then
344                         uri := TIdURI.Create( URL )
345                 else
346                         uri := TIdURI.Create( URL + '/' );
347
348         uriList := TStringList.Create;
349                 try
350                         // http://kanto.machi.to/bbs/read.cgi/kana/1215253035/l50
351                         // http://kanto.machi.to/kana/
352             uriList.Delimiter := '/';
353             uriList.DelimitedText  := uri.Path;
354                         URL := uri.Protocol + '://' + uri.Host + '/';
355             if (uriList.Count >= 4) then begin
356                 URL := URL + uriList[3] + '/';
357             end;
358                         outURL := CreateResultString(URL);
359                 finally
360                         uri.Free;
361             uriList.Free;
362                 end;
363         end else begin
364         outURL := CreateResultString(URL);
365         end;
366
367 end;
368
369
370 // =========================================================================
371 // TMachiBBSThreadItem
372 // =========================================================================
373
374 // *************************************************************************
375 // \83R\83\93\83X\83g\83\89\83N\83^
376 // *************************************************************************
377 constructor TMachiBBSThreadItem.Create(
378         inInstance      : DWORD
379 );
380 var
381         uri                                     : TIdURI;
382         uriList                 : TStringList;
383         FilePath                : String;
384 begin
385
386         inherited;
387
388         OnDownload              := Download;
389         OnWrite                         := Write;
390         OnGetRes                        := GetRes;
391         OnGetDat                        := GetDat;
392         OnGetHeader             := GetHeader;
393         OnGetFooter             := GetFooter;
394         OnGetBoardURL   := GetBoardURL;
395
396         //FFilePath                     := '';
397         FIsTemporary    := False;
398         FDat                                    := nil;
399         URL                                             := ReadURL + '&LAST=50';
400
401         uri                     := TIdURI.Create( URL );
402         uriList := TStringList.Create;
403         try
404                 // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
405                 ExtractHttpFields(
406                         ['&'], [],
407                         Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
408                 FileName        := uriList.Values[ 'KEY' ] + '.dat';
409                 FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
410                 IsLogFile       := FileExists( FilePath );
411         finally
412                 uri.Free;
413                 uriList.Free;
414         end;
415
416 end;
417
418 // *************************************************************************
419 // \83f\83X\83g\83\89\83N\83^
420 // *************************************************************************
421 destructor TMachiBBSThreadItem.Destroy;
422 begin
423
424         FreeDat;
425
426         // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
427         if FIsTemporary then
428                 DeleteFile( FilePath );
429
430         inherited;
431
432 end;
433
434 // *************************************************************************
435 // \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½
436 // *************************************************************************
437 function TMachiBBSThreadItem.Download : TDownloadState;
438 var
439         modified                        : Double;
440         tmp                                             : PChar;
441         downResult              : TStringList;
442         content                         : TStringList;
443         responseCode    : Longint;
444         logStream                       : TFileStream;
445         uri                                             : TIdURI;
446         uriList                         : TStringList;
447         datURL                          : string;
448         foundPos                        : Integer;
449         FilePath                        : String;
450         procedure       downAndParse;
451         begin
452                 responseCode := InternalDownload( PChar( datURL ), modified, tmp, 0 );
453
454                 try
455                         if responseCode = 200 then begin
456                                 downResult      := TStringList.Create;
457                                 try
458                                         downResult.Text := string( tmp );
459
460                                         // \83^\83C\83g\83\8b\82Ì\8eæ\93¾
461                                         foundPos                                := AnsiPos( '<title>', downResult.Text ) + Length( '<title>' );
462                                         Title                                           := Copy(
463                                                 downResult.Text,
464                                                 foundPos,
465                                                 AnsiPos( '</title>', downResult.Text ) - foundPos );
466
467                                         // \83\8c\83X\82Ì\8aJ\8en\88Ê\92u
468                                         foundPos                                := AnsiPos( '<dt', downResult.Text );
469                                         downResult.Text := Copy( downResult.Text, foundPos, Length( downResult.Text ) );
470                                         if foundPos > 0 then begin
471                                                 // \83\8c\83X\82Ì\8fI\97¹\88Ê\92u
472                                                 foundPos := AnsiPos( '<table', downResult.Text ) - 1;
473                                                 if foundPos > 0 then
474                                                         downResult.Text := Copy( downResult.Text, 1, foundPos );
475
476                                                 // \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Å
477                                                 // \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¤
478                                                 To2chDat( downResult, Count + 1 );
479                                                 content.Text := content.Text + downResult.Text;
480                                         end;
481                                 finally
482                                         downResult.Free;
483                                 end;
484                         end else begin
485                                 Result := dsNotModify;
486                                 Exit;
487                         end;
488                 finally
489                         DisposeResultString( tmp );
490                 end;
491         end;
492 begin
493
494         Result := dsError;
495
496         uri                     := TIdURI.Create( URL );
497         uriList := TStringList.Create;
498         content := TStringList.Create;
499         try
500                 ExtractHttpFields(
501                         ['&'], [],
502                         Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
503                 FileName := uriList.Values[ 'KEY' ] + '.dat';
504                 if MyLogFolder = '' then begin
505                         // \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
506                         FilePath                        := TemporaryFile;
507                         FIsTemporary    := True;
508                 end else begin
509                         FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
510                         FIsTemporary    := False;
511                 end;
512
513                 // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
514                 ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
515
516                 // \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Í
517                 // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
518                 modified        := LastModified;
519                 if Count = 0 then
520                         // 1\81`
521                         datURL          :=
522                                 uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
523                                 'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ] +
524                                 '&START=' + IntToStr( 1 )
525                 else
526                         // \90V\92\85\82Ì\82Ý
527                         datURL          :=
528                                 uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
529                                 'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ] +
530                                 '&START=' + IntToStr( Count + 1 ) + '&NOFIRST=TRUE';
531                 // \83_\83E\83\93\83\8d\81[\83h
532                 downAndParse;
533
534                 if content.Count > 0 then begin
535                         if Count <= 0 then begin
536                                 Result := dsComplete;
537                                 // \90V\8bK\8f\91\82«\8d\9e\82Ý
538                                 content[ 0 ]    := content[ 0 ] + Title;
539                                 logStream                       := TFileStream.Create( FilePath, fmCreate or fmShareDenyWrite );
540                                 try
541                                         logStream.Position      := logStream.Size;
542                                         logStream.Write( PChar( content.Text )^, Length( content.Text ) );
543                                 finally
544                                         logStream.Free;
545                                 end;
546                                 NewReceive      := 1;
547                                 Count                           := content.Count;
548                         end else begin
549                 if (content.Count > 1) or (Trim(content.Text) <> '') then begin
550                                 Result := dsDiffComplete;
551                     // \92Ç\8bL
552                     logStream := TFileStream.Create( FilePath, fmOpenReadWrite or fmShareDenyWrite );
553                     try
554                         logStream.Position      := logStream.Size;
555                         logStream.Write( PChar( content.Text )^, Length( content.Text ) );
556                     finally
557                         logStream.Free;
558                     end;
559                     NewReceive  := Count + 1;
560                     Count                               := Count + content.Count;
561                 end else begin
562                     Result := dsNotModify;
563                 end;
564                         end;
565             if (Result <> dsNotModify) then begin
566                         // CGI \82©\82ç\82Í\90³\82µ\82¢\93ú\95t\82ª\93¾\82ç\82ê\82È\82¢\82Ì\82Å\8c»\8dÝ\82É\90Ý\92è
567                         LastModified    := Now;
568                         NewResCount             := content.Count;
569             end;
570                 end else begin
571                         Result := dsNotModify;
572                 end;
573         finally
574                 uri.Free;
575                 uriList.Free;
576                 content.Free;
577         end;
578
579 end;
580
581 // *************************************************************************
582 // \8f\91\82«\8d\9e\82Ý\82ð\8ew\8e¦\82³\82ê\82½
583 // *************************************************************************
584 function        TMachiBBSThreadItem.Write(
585         inName                          : string;       // \96¼\91O(\83n\83\93\83h\83\8b)
586         inMail                          : string;       // \83\81\81[\83\8b\83A\83h\83\8c\83X
587         inMessage                       : string        // \96{\95
588 ) : TDownloadState;                             // \8f\91\82«\8d\9e\82Ý\82ª\90¬\8c÷\82µ\82½\82©\82Ç\82¤\82©
589 var
590         postURL                         : string;
591         postData                        : string;
592         postResult              : PChar;
593         uri                                             : TIdURI;
594         uriList                         : TStringList;
595 begin
596
597         uri                     := TIdURI.Create( URL );
598         uriList := TStringList.Create;
599         try
600                 ExtractHttpFields(
601                         ['&'], [],
602                         Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
603
604                 postURL         := uri.Protocol + '://' + uri.Host + '/bbs/write.cgi';
605                 postData        :=
606                         'NAME='                 + HttpEncode( inName ) +
607                         '&MAIL='                + HttpEncode( inMail ) +
608                         '&MESSAGE='     + HttpEncode( inMessage ) +
609                         '&BBS='                 + uriList.Values[ 'BBS' ] +
610                         '&KEY='                 + uriList.Values[ 'KEY' ] +
611                         '&TIME='                + IntToStr( DateTimeToUnix( Now ) ) +
612                         '&submit='      + HttpEncode( '\8f\91\82«\8d\9e\82Þ' );
613
614                 // \93Æ\8e©\82É\92Ê\90M\82µ\82È\82¢\8fê\8d\87\82Í InternalPost \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
615                 InternalPost( PChar( postURL ), PChar( postData ),PChar(URL), postResult );
616                 DisposeResultString( postResult );
617
618                 Result := dsComplete
619         finally
620                 uri.Free;
621                 uriList.Free;
622         end;
623
624 end;
625
626 // *************************************************************************
627 // \83\8c\83X\94Ô\8d\86 inNo \82É\91Î\82·\82é html \82ð\97v\8b\81\82³\82ê\82½
628 // *************************************************************************
629 function TMachiBBSThreadItem.GetRes(
630         inNo            : Integer               // \97v\8b\81\82³\82ê\82½\83\8c\83X\94Ô\8d\86
631 ) : string;                                             // \91Î\89\9e\82·\82é HTML
632 var
633         res                     : string;
634         tmp                     : PChar;
635 begin
636
637         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
638         // InternalAbon \82¨\82æ\82Ñ Dat2HTML \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
639         LoadDat;
640         if (FDat = nil) or (inNo - 1 < 0 ) or (inNo - 1 >= FDat.Count) then begin
641                 // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
642                 Result := '';
643                 Exit;
644         end;
645         res                     := FDat[ inNo - 1 ];
646         tmp                     := InternalAbonForOne( PChar( res ), PChar(FilePath), inNo);
647     try
648                 Result  := Dat2HTML( string( tmp ), inNo );
649         finally
650                 DisposeResultString( tmp );
651         end;
652
653 end;
654
655 // *************************************************************************
656 // \83\8c\83X\94Ô\8d\86 inNo \82É\91Î\82·\82é Dat \82ð\97v\8b\81\82³\82ê\82½
657 // *************************************************************************
658 function TMachiBBSThreadItem.GetDat(
659         inNo            : Integer               // \97v\8b\81\82³\82ê\82½\83\8c\83X\94Ô\8d\86
660 ) : string;                                             // \82Q\82¿\82á\82ñ\82Ë\82é\82ÌDat\8c`\8e®
661 var
662         //res: string;
663         tmp: PChar;
664 begin
665         //Result        := '';
666         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
667         LoadDat;
668         if (FDat = nil) or (inNo - 1 < 0 ) or (inNo - 1 >= FDat.Count)  then begin
669                 // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
670                 tmp := CreateResultString('');
671                 Result := tmp;
672                 DisposeResultString(tmp);
673                 Exit;
674         end;
675         tmp := CreateResultString(FDat[ inNo - 1]);
676         try
677                 Result := string(tmp);
678         finally
679                 DisposeResultString(tmp);
680         end;
681
682 end;
683
684 // *************************************************************************
685 // \83X\83\8c\83b\83h\82Ì\83w\83b\83_ html \82ð\97v\8b\81\82³\82ê\82½
686 // *************************************************************************
687 function TMachiBBSThreadItem.GetHeader(
688         inOptionalHeader        : string
689 ) : string;
690 begin
691
692         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
693         // InternalHeader \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
694         Result := InternalHeader(
695                 '<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">' +
696                 inOptionalHeader );
697
698
699         // GetRes \82ð\8cÄ\82Î\82ê\82é\82±\82Æ\82ª\97\\91z\82³\82ê\82é\82Ì\82Å FDat \82ð\90\90¬\82µ\82Ä\82¨\82­
700         try
701                 FreeDat;
702                 LoadDat;
703         except
704         end;
705
706 end;
707
708 // *************************************************************************
709 // \83X\83\8c\83b\83h\82Ì\83t\83b\83^ html \82ð\97v\8b\81\82³\82ê\82½
710 // *************************************************************************
711 function TMachiBBSThreadItem.GetFooter(
712         inOptionalFooter : string
713 ) : string;
714 begin
715
716         // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
717         // InternalFooter \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
718         Result := InternalFooter( inOptionalFooter );
719
720         // \82à\82¤ GetRes \82Í\8cÄ\82Î\82ê\82È\82¢\82Æ\8ev\82¤\82Ì\82Å FDat \82ð\8aJ\95ú\82µ\82Ä\82¨\82­
721         try
722                 FreeDat;
723         except
724         end;
725
726 end;
727
728 // *************************************************************************
729 // \82±\82Ì ThreadItem \82ª\91®\82·\82é\94Â\82Ì URL \82ð\97v\8b\81\82³\82ê\82½
730 // *************************************************************************
731 function        TMachiBBSThreadItem.GetBoardURL : string;
732 var
733         uri                                             : TIdURI;
734         uriList                         : TStringList;
735         tmp: PChar;
736 begin
737     tmp := nil;
738         if Copy( URL, Length( URL ), 1 ) = '/' then
739                 uri := TIdURI.Create( URL )
740         else
741                 uri := TIdURI.Create( URL + '/' );
742         uriList := TStringList.Create;
743         try
744                 ExtractHttpFields(
745                         ['&'], [],
746                         Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
747                 FileName := uriList.Values[ 'KEY' ] + '.dat';
748                 // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
749                 // http://hokkaido.machi.to/hokkaidou/
750                 tmp             := CreateResultString(
751                         uri.Protocol + '://' + uri.Host + '/' + uriList.Values[ 'BBS' ] + '/' );
752                 Result := string(tmp);
753         finally
754                 DisposeResultString(tmp);
755                 uri.Free;
756                 uriList.Free;
757         end;
758
759 end;
760
761 // *************************************************************************
762 // \82Ü\82¿BBS\82Ì HTML \82ð 2ch \82Ì dat \8c`\8e®\82É
763 // *************************************************************************
764 procedure       TMachiBBSThreadItem.To2chDat(
765         ioHTML                          : TStringList;
766         inStartNo                       : Integer = 1
767 );
768 var
769         i, bound                        : Integer;
770         foundPos                        : Integer;
771         strTmp                          : string;
772         res                                             : TStringList;
773         no                                              : Integer;
774 const
775         MAIL_TAG                        = '<a href="mailto:';
776 begin
777
778         //===== 2ch \82Ì dat \8c`\8e®\82É\95Ï\8a·
779         // \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­
780         ioHTML.Text     := CustomStringReplace( ioHTML.Text, #13#10, '');
781         //StringReplace( ioHTML.Text, #13#10, '', [rfReplaceAll] );
782         // \91ã\82í\82è\82É <dt> \82ð\8ds\82Ì\8bæ\90Ø\82è\82É\82·\82é
783         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<dt>', #10 );
784         //StringReplace( ioHTML.Text, '<dt>', #10, [rfReplaceAll] );
785         // <dt> \82©\82ç\8en\82Ü\82Á\82Ä\82¢\82é\82Ì\82Å\8dÅ\8f\89\82Í\8bó\82Ì\82Í\82¸
786         if Length( ioHTML[ 0 ] ) = 0 then
787                 ioHTML.Delete( 0 );
788
789         // \8cy\82­\82 \82Ú\81[\82ñ\83`\83F\83b\83N
790         // \81¦\91å\8eG\94c\82¾\82©\82ç\82¿\82á\82ñ\82Æ\8fo\97\88\82Ä\82È\82¢\82©\82à
791         try
792                 i                       := 0;
793                 while i < ioHTML.Count do begin
794                         foundPos := AnsiPos( ' ', ioHTML[ i ] );
795                         if foundPos > 0 then begin
796                                 no := StrToInt( Copy( ioHTML[ i ], 1, foundPos - 1 ) );
797                                 if inStartNo < no then
798                                         ioHTML.Insert( i, '<><><><>' );
799                         end;
800                         Inc( i );
801                         Inc( inStartNo );
802                 end;
803         except
804                 // \82 \82Ú\81[\82ñ\83`\83F\83b\83N\82Å\96â\91è\82ª\94­\90\82µ\82Ä\82à\90æ\82Ö\90i\82ß\82½\82¢\82Ì\82Å
805         end;
806
807         // \83g\83\8a\83b\83v\82Ì\8cã\82Ì '<b> </b>' \82ð\8bó\82É
808     if AnsiPos('\81\9f</b>', ioHTML.Text) <> 0 then begin
809         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<b> </b></font>', '</b></font>', true );
810         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<b> </B></a>', '</b></a>', true );
811     end;
812         //ioHTML.Text   := CustomStringReplace( ioHTML.Text, '<b> </b>', '', true );
813         //StringReplace( ioHTML.Text, '<b> </b>', '', [rfReplaceAll, rfIgnoreCase] );
814         // '<b>' \82Í\83\81\81[\83\8b\82Æ\96¼\91O\82Ì\8bæ\90Ø\82è
815         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<b>', '<>', true );
816         //StringReplace( ioHTML.Text, '<b>', '<>', [rfReplaceAll, rfIgnoreCase] );
817         // \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É
818         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '</b></a>', '<>', true );
819         //StringReplace( ioHTML.Text, '</b></a>', '<>', [rfReplaceAll, rfIgnoreCase] );
820         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '</b>', '<>', true );
821     ioHTML.Text := CustomStringReplace( ioHTML.Text, '\81\9f<>', '\81\9f</b>', true );
822         //StringReplace( ioHTML.Text, '</b>', '<>', [rfReplaceAll, rfIgnoreCase] );
823         // '<dd>' \82ð\96{\95\82Æ\82Ì\8bæ\90Ø\82è\82É
824         ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<dd>', '<>', true );
825         //StringReplace( ioHTML.Text, '<dd>', '<>', [rfReplaceAll, rfIgnoreCase] );
826
827         res := TStringList.Create;
828         try
829                 bound := ioHTML.Count - 1;
830                 for i := 0 to bound do begin
831                         res.Text := CustomStringReplace( ioHTML[ i ], '<>', #10 );
832                                                 //StringReplace( ioHTML[ i ], '<>', #10, [rfReplaceAll] );
833                         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ß
834                                 foundPos := AnsiPos( MAIL_TAG, res[ 0 ] );
835                                 if foundPos > 0 then begin
836                                         // \83\81\81[\83\8b\83A\83h\83\8c\83X\82ð\94²\82«\8fo\82·
837                                         foundPos        := foundPos + Length( MAIL_TAG );
838                                         res[ 0 ]        := Copy( res[ 0 ], foundPos, Length( res[ 0 ] ) );
839                                         strTmp          := Copy( res[ 0 ], 1, AnsiPos( '">', res[ 0 ] ) - 1 );
840                                         // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82µ\82Ä\96ß\82·
841                                         res[ 0 ]        := res[ 1 ];
842                                         res[ 1 ]        := strTmp;
843                                 end else begin
844                                         // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82·
845                                         res[ 0 ]        := res[ 1 ];
846                                         res[ 1 ]        := '';
847                                 end;
848                                 res[ 2 ] := StringReplace( res[ 2 ], '[', 'IP:', [] );
849                                 res[ 2 ] := StringReplace( res[ 2 ], ']', '', [] );
850
851                 if AnsiPos('</font> \93\8a\8de\93ú\81F', res[ 2 ]) = 1 then begin
852                         res[ 2 ] := StringReplace( res[ 2 ], '</font> \93\8a\8de\93ú\81F', '', [] );
853                 end else if AnsiPos(' \93\8a\8de\93ú\81F', res[ 2 ]) = 1 then begin
854                     res[ 2 ] := StringReplace( res[ 2 ], ' \93\8a\8de\93ú\81F', '', [] );
855                 end;
856                         end;
857                         ioHTML[ i ] := CustomStringReplace( res.Text, #13#10, '<>');
858                         //StringReplace( res.Text, #13#10, '<>', [rfReplaceAll] );
859                 end;
860         finally
861                 res.Free;
862         end;
863
864 end;
865
866 // *************************************************************************
867 // FDat \82Ì\90\90¬
868 // *************************************************************************
869 procedure       TMachiBBSThreadItem.LoadDat;
870 begin
871
872         if FDat = nil then begin
873                 if IsLogFile then begin
874                         // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
875                         FDat := TStringList.Create;
876                         FDat.LoadFromFile( FilePath );
877                 end;
878         end;
879
880 end;
881
882 // *************************************************************************
883 // FDat \82Ì\8aJ\95ú
884 // *************************************************************************
885 procedure       TMachiBBSThreadItem.FreeDat;
886 begin
887
888         if FDat <> nil then begin
889                 FDat.Free;
890                 FDat := nil;
891         end;
892
893 end;
894
895 // *************************************************************************
896 // \88À\91S\82È( '/' \82Å\8fI\82í\82é )\93Ç\82Ý\8d\9e\82Ý\82Ì URL
897 // *************************************************************************
898 function        TMachiBBSThreadItem.ReadURL : string;
899 var
900         uri                             : TIdURI;
901         uriList         : TStringList;
902         foundPos        : Integer;
903 const
904     THREAD_MARK2= '/bbs/read.cgi';
905 begin
906
907         foundPos := AnsiPos( '?', URL );
908         if foundPos > 0 then begin
909                 uri := TIdURI.Create( URL );
910                 uriList := TStringList.Create;
911                 try
912                         ExtractHttpFields( ['&'], [], Copy( URL, foundPos + 1, MaxInt ), uriList );
913                         Result :=
914                                 uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
915                                 'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ];
916                 finally
917                         uri.Free;
918                         uriList.Free;
919                 end;
920         end else begin
921         // \90V\8c`\8e® ?
922         foundPos := AnsiPos(THREAD_MARK2, URL);
923         if (foundPos > 0) then begin
924             uri := TIdURI.Create( URL );
925             uriList := TStringList.Create;
926             try
927                 uriList.Delimiter := '/';
928                 uriList.DelimitedText  := uri.Path;
929                 if (uriList.Count >= 5) then begin
930                             Result :=
931                                     uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
932                                     'BBS=' + uriList[3] + '&KEY=' + uriList[4];
933                 end;
934             finally
935                         uri.Free;
936                         uriList.Free;
937             end;
938         end;
939     end;
940
941 end;
942
943 // *************************************************************************
944 // TThreadItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\90\90¬\82·\82é)
945 // *************************************************************************
946 procedure ThreadItemOnCreateOfTMachiBBSThreadItem(
947         inInstance : DWORD
948 );
949 var
950         threadItem : TMachiBBSThreadItem;
951 begin
952
953         threadItem := TMachiBBSThreadItem.Create( inInstance );
954         ThreadItemSetLong( inInstance, tipContext, DWORD( threadItem ) );
955
956 end;
957
958 // *************************************************************************
959 // TThreadItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\94j\8aü\82·\82é)
960 // *************************************************************************
961 procedure ThreadItemOnDisposeOfTMachiBBSThreadItem(
962         inInstance : DWORD
963 );
964 var
965         threadItem : TMachiBBSThreadItem;
966 begin
967
968         threadItem := TMachiBBSThreadItem( ThreadItemGetLong( inInstance, tipContext ) );
969         threadItem.Free;
970
971 end;
972
973 // =========================================================================
974 // TMachiBBSBoardItem
975 // =========================================================================
976
977 // *************************************************************************
978 // \83R\83\93\83X\83g\83\89\83N\83^
979 // *************************************************************************
980 constructor TMachiBBSBoardItem.Create(
981         inInstance      : DWORD
982 );
983 var
984         uri                                     : TIdURI;
985         uriList                 : TStringList;
986 begin
987
988         inherited;
989
990         OnDownload                                              := Download;
991         OnCreateThread                          := CreateThread;
992         OnEnumThread                                    := EnumThread;
993         OnFileName2ThreadURL    := ToThreadURL;
994
995         FilePath                        := '';
996         FIsTemporary    := False;
997         FDat                                    := nil;
998     Is2ch                       := False;
999
1000         uri                     := TIdURI.Create( SubjectURL );
1001         uriList := TStringList.Create;
1002         try
1003                 ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
1004                 // http://hokkaido.machi.to/hokkaidou/subject.txt
1005                 FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
1006                 IsLogFile       := FileExists( FilePath );
1007         finally
1008                 uri.Free;
1009                 uriList.Free;
1010         end;
1011
1012 end;
1013  
1014 // *************************************************************************
1015 // \83f\83X\83g\83\89\83N\83^
1016 // *************************************************************************
1017 destructor TMachiBBSBoardItem.Destroy;
1018 begin
1019
1020         if FDat <> nil then begin
1021                 try
1022                         FDat.Free;
1023                         FDat := nil;
1024                 except
1025                 end;
1026         end;
1027
1028         // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
1029         if FIsTemporary then
1030                 DeleteFile( FilePath );
1031
1032         inherited;
1033
1034 end;
1035
1036 // *************************************************************************
1037 // \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½
1038 // *************************************************************************
1039 function TMachiBBSBoardItem.Download : TDownloadState;
1040 var
1041         modified                        : Double;
1042         downResult              : PChar;
1043         responseCode    : Longint;
1044         uri                                             : TIdURI;
1045         uriList                         : TStringList;
1046 begin
1047
1048         Result := dsError;
1049
1050         if FDat <> nil then begin
1051                 try
1052                         FDat.Free;
1053                         FDat := nil;
1054                 except
1055                 end;
1056         end;
1057         FDat            := TStringList.Create;
1058         uri                     := TIdURI.Create( SubjectURL );
1059         uriList := TStringList.Create;
1060         // \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Í
1061         // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
1062         modified                        := LastModified;
1063         responseCode    := InternalDownload( PChar( uri.URI ), modified, downResult );
1064         try
1065                 if responseCode = 200 then begin
1066                         try
1067                                 // \83p\83X\82ð\8eZ\8fo
1068                                 ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
1069                                 if MyLogFolder = '' then begin
1070                                         // \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
1071                                         FilePath                        := TemporaryFile;
1072                                         FIsTemporary    := True;
1073                                 end else begin
1074                                         FilePath                        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
1075                                         FIsTemporary    := False
1076                                 end;
1077
1078                                 // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
1079                                 ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
1080
1081                                 FDat.Text := string( downResult );
1082                                 // \95Û\91
1083                                 FDat.SaveToFile( FilePath );
1084
1085                                 IsLogFile                       := True;
1086                                 RoundDate                       := Now;
1087                                 LastModified    := modified;
1088                                 LastGetTime             := Now;
1089                         finally
1090                                 uri.Free;
1091                                 uriList.Free;
1092                         end;
1093                         Result := dsComplete;
1094                 end;
1095         finally
1096                 DisposeResultString( downResult );
1097         end;
1098
1099 end;
1100
1101 // *************************************************************************
1102 // \83X\83\8c\97§\82Ä\82ð\8ew\8e¦\82³\82ê\82½
1103 // *************************************************************************
1104 function        TMachiBBSBoardItem.CreateThread(
1105         inSubject                       : string;       // \83X\83\8c\83^\83C
1106         inName                          : string;       // \96¼\91O(\83n\83\93\83h\83\8b)
1107         inMail                          : string;       // \83\81\81[\83\8b\83A\83h\83\8c\83X
1108         inMessage                       : string        // \96{\95
1109 ) : TDownloadState;                             // \8f\91\82«\8d\9e\82Ý\82ª\90¬\8c÷\82µ\82½\82©\82Ç\82¤\82©
1110 var
1111         postURL                         : string;
1112         postData                        : string;
1113         postResult              : PChar;
1114         uri                                             : TIdURI;
1115         uriList                         : TStringList;
1116 begin
1117
1118         uri                     := TIdURI.Create( URL );
1119         uriList := TStringList.Create;
1120         try
1121                 ExtractHttpFields(
1122                         ['&'], [],
1123                         Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
1124
1125                 postURL         := uri.Protocol + '://' + uri.Host + '/bbs/write.cgi';
1126                 postData        :=
1127                         'SUBJECT='      + HttpEncode( inSubject ) +
1128                         '&NAME='                + HttpEncode( inName ) +
1129                         '&MAIL='                + HttpEncode( inMail ) +
1130                         '&MESSAGE='     + HttpEncode( inMessage ) +
1131                         '&BBS='                 + uriList[ 1 ] +
1132                         '&TIME='                + IntToStr( DateTimeToUnix( Now ) ) +
1133                         '&submit='      + HttpEncode( '\90V\8bK\8f\91\82«\8d\9e\82Ý' );
1134
1135                 // \93Æ\8e©\82É\92Ê\90M\82µ\82È\82¢\8fê\8d\87\82Í InternalPost \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
1136                 InternalPost( PChar( postURL ), PChar( postData ),PChar(URL), postResult );
1137                 DisposeResultString( postResult );
1138
1139                 Result := dsComplete
1140         finally
1141                 uri.Free;
1142                 uriList.Free;
1143         end;
1144
1145 end;
1146
1147 // *************************************************************************
1148 // \83X\83\8c\88ê\97\97\82Ì URL \82©\82ç\83X\83\8c\83b\83h\82Ì URL \82ð\93±\82«\8fo\82·
1149 // *************************************************************************
1150 function TMachiBBSBoardItem.ToThreadURL(
1151         inFileName      : string        // \83X\83\8c\83b\83h\83t\83@\83C\83\8b\96¼
1152 ) : string;                                                     // \83X\83\8c\83b\83h\82Ì URL
1153 var
1154         threadURL               : string;
1155         uri                                     : TIdURI;
1156         uriList                 : TStringList;
1157         found                           : Integer;
1158 begin
1159
1160         found := AnsiPos( '.', inFileName );
1161         if found > 0 then
1162                 inFileName := Copy( inFileName, 1, found - 1 );
1163
1164         uri                     := TIdURI.Create( SubjectURL );
1165         uriList := TStringList.Create;
1166         try
1167                 try
1168                         // http://hokkaido.machi.to/hokkaidou/
1169                         // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
1170                         ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
1171                         threadURL       := uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
1172                                 'BBS=' + uriList[ 1 ] + '&KEY=' + inFileName + '&LAST=50';
1173                         Result          := threadURL;
1174                 finally
1175                         uri.Free;
1176                         uriList.Free;
1177                 end;
1178         except
1179                 Result := '';
1180         end;
1181
1182 end;
1183
1184 // *************************************************************************
1185 // \82±\82Ì\94Â\82É\82¢\82­\82Â\82Ì\83X\83\8c\82ª\82 \82é\82©\97v\8b\81\82³\82ê\82½
1186 // *************************************************************************
1187 procedure       TMachiBBSBoardItem.EnumThread(
1188         inCallBack      : TBoardItemEnumThreadCallBack
1189 );
1190 var
1191         uri                                     : TIdURI;
1192         uriList                 : TStringList;
1193 begin
1194
1195         try
1196                 if FDat = nil then begin
1197                         FDat := TStringList.Create;
1198
1199                         uri                     := TIdURI.Create( SubjectURL );
1200                         uriList := TStringList.Create;
1201                         try
1202                                 // \83p\83X\82ð\8eZ\8fo
1203                                 ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
1204                                 // http://hokkaido.machi.to/hokkaidou/subject.txt
1205                                 FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
1206                                 if FileExists( FilePath ) then
1207                                         // \93Ç\82Ý\8d\9e\82Ý
1208                                         FDat.LoadFromFile( FilePath );
1209                         finally
1210                                 uri.Free;
1211                                 uriList.Free;
1212                         end;
1213                 end;
1214
1215                 // \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é
1216                 inherited EnumThread( inCallBack, CustomStringReplace( FDat.Text, ',', '<>' ) );
1217         except
1218         end;
1219
1220 end;
1221
1222 // *************************************************************************
1223 // \83X\83\8c\88ê\97\97\82Ì URL \82ð\8b\81\82ß\82é
1224 // *************************************************************************
1225 function        TMachiBBSBoardItem.SubjectURL : string;
1226 var
1227         uri                     : TIdURI;
1228         uriList : TStringList;
1229 begin
1230
1231         uri                     := TIdURI.Create( URL );
1232         uriList := TStringList.Create;
1233         try
1234                 if uri.Document <> SUBJECT_NAME then begin
1235                         if Copy( URL, Length( URL ), 1 ) = '/' then
1236                                 Result := URL + SUBJECT_NAME
1237                         else
1238                                 Result := URL + '/' + SUBJECT_NAME;
1239                 end else begin
1240                         // \82±\82±\82É\82Í\97\88\82È\82¢\82Æ\8ev\82¤\82¯\82Ç
1241                         Result := URL;
1242                 end;
1243         finally
1244                 uri.Free;
1245                 uriList.Free;
1246         end;
1247
1248 end;
1249
1250 // *************************************************************************
1251 // TBoardItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\90\90¬\82·\82é)
1252 // *************************************************************************
1253 procedure BoardItemOnCreateOfTMachiBBSBoardItem(
1254         inInstance : DWORD
1255 );
1256 var
1257         boardItem : TMachiBBSBoardItem;
1258 begin
1259
1260         boardItem := TMachiBBSBoardItem.Create( inInstance );
1261         BoardItemSetLong( inInstance, bipContext, DWORD( boardItem ) );
1262
1263 end;
1264
1265 // *************************************************************************
1266 // TBoardItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\94j\8aü\82·\82é)
1267 // *************************************************************************
1268 procedure BoardItemOnDisposeOfTMachiBBSBoardItem(
1269         inInstance : DWORD
1270 );
1271 var
1272         boardItem : TMachiBBSBoardItem;
1273 begin
1274
1275         boardItem := TMachiBBSBoardItem( BoardItemGetLong( inInstance, bipContext ) );
1276         boardItem.Free;
1277
1278 end;
1279
1280
1281
1282 // =========================================================================
1283 // \83G\83\93\83g\83\8a\83|\83C\83\93\83g
1284 // =========================================================================
1285 procedure DLLEntry(
1286         ul_reason_for_call : DWORD
1287 );
1288 var
1289         module : HMODULE;
1290 begin
1291
1292         case ul_reason_for_call of
1293                 DLL_PROCESS_ATTACH:
1294                 begin
1295                         Randomize;
1296
1297                         module := GetModuleHandle( nil );
1298
1299                         LoadInternalAPI( module );
1300                         LoadInternalFilePathAPI( module );
1301                         LoadInternalThreadItemAPI( module );
1302                         LoadInternalBoardItemAPI( module );
1303
1304                         // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TThreadItem \82©\82ç TMachiBBSThreadItem \82É\95Ï\8dX\82·\82é
1305                         ThreadItemOnCreate      := ThreadItemOnCreateOfTMachiBBSThreadItem;
1306                         ThreadItemOnDispose     := ThreadItemOnDisposeOfTMachiBBSThreadItem;
1307                         // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TBoardItem \82©\82ç TMachiBBSBoardItem \82É\95Ï\8dX\82·\82é
1308                         BoardItemOnCreate               := BoardItemOnCreateOfTMachiBBSBoardItem;
1309                         BoardItemOnDispose      := BoardItemOnDisposeOfTMachiBBSBoardItem;
1310                 end;
1311                 DLL_PROCESS_DETACH:
1312                         ;
1313                 DLL_THREAD_ATTACH:
1314                         ;
1315                 DLL_THREAD_DETACH:
1316                         ;
1317         end;
1318
1319 end;
1320
1321 exports
1322         OnVersionInfo,
1323         OnAcceptURL,
1324     OnExtractBoardURL;
1325 begin
1326
1327         try
1328                 DllProc := @DLLEntry;
1329                 DLLEntry( DLL_PROCESS_ATTACH );
1330         except end;
1331
1332 end.