OSDN Git Service

1.60.0.783をマージ
[gikonavigoeson/gikonavi.git] / res / ExternalBoardPlugIn / MachiBBSPlugIn.dpr
index 29f12f0..1956c36 100644 (file)
@@ -3,21 +3,16 @@ library MachiBBSPlugIn;
 {
        MachiBBSBoardPlugIn
        \82Ü\82¿BBS\8f\88\97\9d\83\86\83j\83b\83g
-       \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
-       \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
 }
 
 uses
-       Windows,
-       SysUtils,
-       Classes,
-       Math,
+       Windows, SysUtils, Classes, Math, DateUtils,
        IdURI,
-       YofUtils in '..\..\YofUtils.pas',
        PlugInMain in 'PlugInMain.pas',
        ThreadItem in 'ThreadItem.pas',
        BoardItem in 'BoardItem.pas',
-       FilePath in 'FilePath.pas';
+       FilePath in 'FilePath.pas',
+    MojuUtils in '..\..\MojuUtils.pas';
 
 {$R *.res}
 
@@ -29,18 +24,25 @@ type
        private
                FIsTemporary    : Boolean;
                FDat                                    : TStringList;
-
+               //FFilePath             : String;
        public
                constructor     Create( inInstance : DWORD );
                destructor      Destroy; override;
 
        private
                function        Download : TDownloadState;
+               function        Write( inName : string; inMail : string; inMessage : string ) : TDownloadState;
                function        GetRes( inNo : Integer ) : string;
+               function        GetDat( inNo : Integer ) : string;
                function        GetHeader( inOptionalHeader : string ) : string;
                function        GetFooter( inOptionalFooter : string ) : string;
+               function        GetBoardURL : string;
 
-               procedure       To2chDat( ioHTML : TStringList );
+               procedure       To2chDat( ioHTML : TStringList; inStartNo : Integer = 1 );
+               procedure       LoadDat;
+               procedure       FreeDat;
+               function        ReadURL : string;
+               //property      FilePath : string read FFilePath;
        end;
 
        // =========================================================================
@@ -57,9 +59,11 @@ type
 
        private
                function        Download : TDownloadState;
+               function        CreateThread( inSubject : string; inName : string; inMail : string; inMessage : string ) : TDownloadState;
                function        ToThreadURL( inFileName : string ) : string;
                procedure       EnumThread( inCallBack : TBoardItemEnumThreadCallBack );
 
+               function        SubjectURL : string;
        end;
 
        // =========================================================================
@@ -73,12 +77,13 @@ type
 
 const
        LOG_DIR                                         = 'MachiBBS\';
+       SUBJECT_NAME                    = 'subject.txt';
 
        PLUGIN_NAME                             = 'MachiBBSPlugIn';
        MAJOR_VERSION                   = 1;
        MINOR_VERSION                   = 0;
-       RELEASE_VERSION         = 'alpha';
-       REVISION_VERSION        = 1;
+       RELEASE_VERSION         = 'beta';
+       REVISION_VERSION        = 20;
 
 // =========================================================================
 // \8eG\97p\8aÖ\90\94
@@ -104,7 +109,7 @@ end;
 // *************************************************************************
 function MyLogFolder : string;
 var
-       folder : string;
+       folder : PChar;
 begin
 
        folder := LogFolder;
@@ -112,6 +117,7 @@ begin
                Result := ''
        else
                Result := folder + LOG_DIR;
+    DisposeResultString(folder);
 
 end;
 
@@ -140,6 +146,82 @@ begin
        Result := ForceDirectoriesEx(ExtractFilePath(Dir)) and CreateDir(Dir);
 end;
 
+// \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
+procedure ExtractHttpFields(
+       const chrSep : TSysCharSet;
+       const chrWhite : TSysCharSet;
+       const strValue : string;
+       var strResult : TStringList;
+       unknownFlag : boolean = false
+);
+var
+       last, p, strLen : Integer;
+begin
+
+       strLen := Length( strValue );
+       p := 1;
+       last := 1;
+
+       while p <= strLen do
+       begin
+
+               if strValue[ p ] in chrSep then
+               begin
+                       strResult.Add( Copy( strValue, last, p - last ) );
+                       last := p + 1;
+               end;
+
+               p := p + 1;
+
+       end;
+
+       if last <> p then
+               strResult.Add( Copy( strValue, last, strLen - last + 1 ) );
+
+end;
+
+\rfunction HttpEncode(
+\r      const strValue : string
+) : string;
+var
+       i : Integer;
+       strLen : Integer;
+       strResult : string;
+       b : Integer;
+const
+       kHexCode : array [0..15] of char = (
+                               '0', '1', '2', '3', '4', '5', '6', '7',
+                               '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' );
+begin
+
+       strLen := Length( strValue );
+       i := 1;
+
+       while i <= strLen do
+       begin
+
+               case strValue[ i ] of
+               '0' .. '9', 'a' .. 'z', 'A' .. 'Z', '*', '-', '.', '@', '_':
+                       begin
+                               strResult := strResult + strValue[ i ];
+                       end;
+               else
+                       begin
+                               b := Integer( strValue[ i ] );
+                               strResult := strResult + '%'
+                                                               + kHexCode[ b div $10 ]
+                                                               + kHexCode[ b mod $10 ];
+                       end;
+               end;
+
+               i := i + 1;
+
+       end;
+
+       Result := strResult;
+
+end;
+
 
 
 // =========================================================================
@@ -178,30 +260,111 @@ end;
 // \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©
 // *************************************************************************
 function OnAcceptURL(
-       inURL : PChar                                           // \94»\92f\82ð\8bÂ\82¢\82Å\82¢\82é URL
-): Boolean; stdcall;                   // \8eó\82¯\95t\82¯\82é\82È\82ç True
+       inURL                   : PChar                         // \94»\92f\82ð\8bÂ\82¢\82Å\82¢\82é URL
+): TAcceptType; stdcall;       // URL \82Ì\8eí\97Þ
 var
-       URI : TIdURI;
-       foundPos : Integer;
+       uri                             : TIdURI;
+       uriList         : TStringList;
+       foundPos        : Integer;
 const
-       BBS_HOST = 'machi.to';
+       BBS_HOST                = 'machi.to';
+       THREAD_MARK     = '/bbs/read.pl';
+    THREAD_MARK2= '/bbs/read.cgi';
 begin
 
        try
-               // \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é
-               URI := TIdURI.Create( inURL );
+               // \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é
+               uri                     := TIdURI.Create( inURL );
+               uriList := TStringList.Create;
                try
-                       foundPos := Pos( BBS_HOST, URI.Host );
-                       Result := (foundPos > 0) and (Length( URI.Host ) - foundPos + 1 = Length( BBS_HOST ))
+                       ExtractHttpFields( ['/'], [], uri.Path, uriList );
+                       foundPos := AnsiPos( BBS_HOST, uri.Host );
+                       if (foundPos > 0) and (Length( uri.Host ) - foundPos + 1 = Length( BBS_HOST )) then begin
+                               foundPos := Pos( THREAD_MARK, inURL );
+                if (foundPos = 0) then begin
+                    // \90VURL\91Î\89\9e
+                    foundPos := Pos( THREAD_MARK2, inURL );
+                end;
+                               if foundPos > 0 then
+                                       Result := atThread
+                               else if (uriList.Count > 1) and (uri.Path <> '/') then  // \8dÅ\8cã\82ª '/' \82Å\95Â\82ß\82ç\82ê\82Ä\82é\82È\82ç 3
+                                       Result := atBoard
+                               else
+                                       Result := atBBS;
+                       end else begin
+                               Result := atNoAccept;
+                       end;
                finally
-                       URI.Free;
+                       uri.Free;
+                       uriList.Free;
                end;
        except
-               Result := False;
+               Result := atNoAccept;
        end;
 
 end;
 
+// *************************************************************************
+// \8ew\92è\82µ\82½ URL \82ðBoard\82ÌURL\82É\95Ï\8a·
+// *************************************************************************
+procedure OnExtractBoardURL(
+       inURL   : PChar;
+       var outURL      : PChar
+); stdcall;
+var
+       uri                     : TIdURI;
+       uriList         : TStringList;
+       URL         : String;
+const
+       THREAD_MARK     = '/bbs/read.pl';
+    THREAD_MARK2= '/bbs/read.cgi';
+begin
+       URL := string(inURL);
+       if AnsiPos(THREAD_MARK, URL) > 0 then begin
+               if Copy( inURL, Length( inURL ), 1 ) = '/' then
+                       uri := TIdURI.Create( URL )
+               else
+                       uri := TIdURI.Create( URL + '/' );
+
+               uriList := TStringList.Create;
+               try
+                       ExtractHttpFields(
+                               ['&'], [],
+                               Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ),uriList );
+                       // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
+                       // http://hokkaido.machi.to/hokkaidou/
+                       URL := uri.Protocol + '://' + uri.Host + '/' + uriList.Values[ 'BBS' ] + '/';
+                       outURL := CreateResultString(URL);
+               finally
+                       uri.Free;
+                       uriList.Free;
+               end;
+    end else if AnsiPos(THREAD_MARK2, URL) > 0 then begin
+               if Copy( inURL, Length( inURL ), 1 ) = '/' then
+                       uri := TIdURI.Create( URL )
+               else
+                       uri := TIdURI.Create( URL + '/' );
+
+        uriList := TStringList.Create;
+               try
+                       // http://kanto.machi.to/bbs/read.cgi/kana/1215253035/l50
+                       // http://kanto.machi.to/kana/
+            uriList.Delimiter := '/';
+            uriList.DelimitedText  := uri.Path;
+                       URL := uri.Protocol + '://' + uri.Host + '/';
+            if (uriList.Count >= 4) then begin
+                URL := URL + uriList[3] + '/';
+            end;
+                       outURL := CreateResultString(URL);
+               finally
+                       uri.Free;
+            uriList.Free;
+               end;
+       end else begin
+       outURL := CreateResultString(URL);
+       end;
+
+end;
 
 
 // =========================================================================
@@ -217,26 +380,31 @@ constructor TMachiBBSThreadItem.Create(
 var
        uri                                     : TIdURI;
        uriList                 : TStringList;
+       FilePath                : String;
 begin
 
        inherited;
 
        OnDownload              := Download;
+       OnWrite                         := Write;
        OnGetRes                        := GetRes;
+       OnGetDat                        := GetDat;
        OnGetHeader             := GetHeader;
        OnGetFooter             := GetFooter;
+       OnGetBoardURL   := GetBoardURL;
 
-       FilePath                        := '';
+       //FFilePath                     := '';
        FIsTemporary    := False;
        FDat                                    := nil;
+       URL                                             := ReadURL + '&LAST=50';
 
        uri                     := TIdURI.Create( URL );
        uriList := TStringList.Create;
        try
                // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
-               YofUtils.ExtractHttpFields(
+               ExtractHttpFields(
                        ['&'], [],
-                       Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+                       Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
                FileName        := uriList.Values[ 'KEY' ] + '.dat';
                FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
                IsLogFile       := FileExists( FilePath );
@@ -253,13 +421,7 @@ end;
 destructor TMachiBBSThreadItem.Destroy;
 begin
 
-       if FDat <> nil then begin
-               try
-                       FDat.Free;
-                       FDat := nil;
-               except
-               end;
-       end;
+       FreeDat;
 
        // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
        if FIsTemporary then
@@ -277,22 +439,67 @@ var
        modified                        : Double;
        tmp                                             : PChar;
        downResult              : TStringList;
+       content                         : TStringList;
        responseCode    : Longint;
        logStream                       : TFileStream;
        uri                                             : TIdURI;
        uriList                         : TStringList;
        datURL                          : string;
        foundPos                        : Integer;
+       FilePath                        : String;
+       procedure       downAndParse;
+       begin
+               responseCode := InternalDownload( PChar( datURL ), modified, tmp, 0 );
+
+               try
+                       if responseCode = 200 then begin
+                               downResult      := TStringList.Create;
+                               try
+                                       downResult.Text := string( tmp );
+
+                                       // \83^\83C\83g\83\8b\82Ì\8eæ\93¾
+                                       foundPos                                := AnsiPos( '<title>', downResult.Text ) + Length( '<title>' );
+                                       Title                                           := Copy(
+                                               downResult.Text,
+                                               foundPos,
+                                               AnsiPos( '</title>', downResult.Text ) - foundPos );
+
+                                       // \83\8c\83X\82Ì\8aJ\8en\88Ê\92u
+                                       foundPos                                := AnsiPos( '<dt', downResult.Text );
+                                       downResult.Text := Copy( downResult.Text, foundPos, Length( downResult.Text ) );
+                                       if foundPos > 0 then begin
+                                               // \83\8c\83X\82Ì\8fI\97¹\88Ê\92u
+                                               foundPos := AnsiPos( '<table', downResult.Text ) - 1;
+                                               if foundPos > 0 then
+                                                       downResult.Text := Copy( downResult.Text, 1, foundPos );
+                                               // \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Å
+                                               // \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¤
+                                               To2chDat( downResult, Count + 1 );
+                                               content.Text := content.Text + downResult.Text;
+                                       end;
+                               finally
+                                       downResult.Free;
+                               end;
+                       end else begin
+                               Result := dsNotModify;
+                               Exit;
+                       end;
+               finally
+                       DisposeResultString( tmp );
+               end;
+       end;
 begin
 
        Result := dsError;
 
        uri                     := TIdURI.Create( URL );
-       uriList := TStringList.Create;
+       uriList := TStringList.Create;
+       content := TStringList.Create;
        try
-               YofUtils.ExtractHttpFields(
+               ExtractHttpFields(
                        ['&'], [],
-                       Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+                       Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+               FileName := uriList.Values[ 'KEY' ] + '.dat';
                if MyLogFolder = '' then begin
                        // \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
                        FilePath                        := TemporaryFile;
@@ -305,75 +512,112 @@ begin
                // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
                ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
 
-               if FileExists( FilePath ) then
-                       logStream := TFileStream.Create( FilePath, fmOpenReadWrite or fmShareDenyWrite )
+               // \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Í
+               // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
+               modified        := LastModified;
+               if Count = 0 then
+                       // 1\81`
+                       datURL          :=
+                               uri.Protocol + '://' + uri.Host + '/bbs/read.cgi?' +
+                               'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ] +
+                               '&START=' + IntToStr( 1 )
                else
-                       logStream := TFileStream.Create( FilePath, fmCreate or fmShareDenyWrite );
-               try
-                       // \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Í
-                       // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
-                       modified        := LastModified;
-                       // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
-                       // 1 + \90V\92\85
+                       // \90V\92\85\82Ì\82Ý
                        datURL          :=
-                               uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
+                               uri.Protocol + '://' + uri.Host + '/bbs/read.cgi?' +
                                'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ] +
-                               '&START=' + IntToStr( Count + 1 );
-                       responseCode := InternalDownload( PChar( datURL ), modified, tmp, 0 );
-
-                       try
-                               if responseCode = 200 then begin
-                                       downResult := TStringList.Create;
-                                       try
-                                               downResult.Text := string( tmp );
-
-                                               // \83\8c\83X\82Ì\8aJ\8en\88Ê\92u
-                                               foundPos                                := Pos( '<dt', downResult.Text );
-                                               downResult.Text := Copy( downResult.Text, foundPos, Length( downResult.Text ) );
-                                               if foundPos > 0 then begin
-                                                       // \83\8c\83X\82Ì\8fI\97¹\88Ê\92u
-                                                       foundPos := Pos( '<table', downResult.Text ) - 1;
-                                                       if foundPos > 0 then
-                                                               downResult.Text := Copy( downResult.Text, 1, foundPos );
-
-                                                       // \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Å
-                                                       // \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¤
-                                                       To2chDat( downResult );
-
-                                                       if Count = 0 then begin
-                                                               Result := dsComplete
-                                                       end else begin
-                                                               if downResult.Count > 0 then
-                                                                       // \8dÅ\8f\89\82Í\95K\82¸ 1 \82Ì\83\8c\83X
-                                                                       downResult.Delete( 0 );
-                                                               Result := dsDiffComplete;
-                                                       end;
-                                                       if downResult.Count > 0 then begin
-                                                               logStream.Position      := logStream.Size;
-                                                               logStream.Write( PChar( downResult.Text )^, Length( downResult.Text ) );
-
-                                                               LastModified                            := modified;
-                                                               IsLogFile                                               := True;
-                                                               NewReceive                                      := Count + 1;
-                                                               Count                                                           := Count + downResult.Count;
-                                                               NewResCount                                     := downResult.Count;
-                                                       end else begin
-                                                               Result                                                  := dsNotModify;
-                                                       end;
-                                               end;
-                                       finally
-                                               downResult.Free;
-                                       end;
+                               '&START=' + IntToStr( Count + 1 ) + '&NOFIRST=TRUE';
+               // \83_\83E\83\93\83\8d\81[\83h
+               downAndParse;
+
+               if content.Count > 0 then begin
+                       if Count <= 0 then begin
+                               Result := dsComplete;
+                               // \90V\8bK\8f\91\82«\8d\9e\82Ý
+                               content[ 0 ]    := content[ 0 ] + Title;
+                               logStream                       := TFileStream.Create( FilePath, fmCreate or fmShareDenyWrite );
+                               try
+                                       logStream.Position      := logStream.Size;
+                                       logStream.Write( PChar( content.Text )^, Length( content.Text ) );
+                               finally
+                                       logStream.Free;
                                end;
-                       finally
-                               DisposeResultString( tmp );
+                               NewReceive      := 1;
+                               Count                           := content.Count;
+                       end else begin
+                if (content.Count > 1) or (Trim(content.Text) <> '') then begin
+                               Result := dsDiffComplete;
+                    // \92Ç\8bL
+                    logStream := TFileStream.Create( FilePath, fmOpenReadWrite or fmShareDenyWrite );
+                    try
+                        logStream.Position     := logStream.Size;
+                        logStream.Write( PChar( content.Text )^, Length( content.Text ) );
+                    finally
+                        logStream.Free;
+                    end;
+                    NewReceive := Count + 1;
+                    Count                              := Count + content.Count;
+                end else begin
+                    Result := dsNotModify;
+                end;
                        end;
-               finally
-                       logStream.Free;
+            if (Result <> dsNotModify) then begin
+                       // CGI \82©\82ç\82Í\90³\82µ\82¢\93ú\95t\82ª\93¾\82ç\82ê\82È\82¢\82Ì\82Å\8c»\8dÝ\82É\90Ý\92è
+                       LastModified    := Now;
+                       NewResCount             := content.Count;
+            end;
+               end else begin
+                       Result := dsNotModify;
                end;
        finally
                uri.Free;
                uriList.Free;
+               content.Free;
+       end;
+
+end;
+
+// *************************************************************************
+// \8f\91\82«\8d\9e\82Ý\82ð\8ew\8e¦\82³\82ê\82½
+// *************************************************************************
+function       TMachiBBSThreadItem.Write(
+       inName                          : string;       // \96¼\91O(\83n\83\93\83h\83\8b)
+       inMail                          : string;       // \83\81\81[\83\8b\83A\83h\83\8c\83X
+       inMessage                       : string        // \96{\95
+) : TDownloadState;                            // \8f\91\82«\8d\9e\82Ý\82ª\90¬\8c÷\82µ\82½\82©\82Ç\82¤\82©
+var
+       postURL                         : string;
+       postData                        : string;
+       postResult              : PChar;
+       uri                                             : TIdURI;
+       uriList                         : TStringList;
+begin
+
+       uri                     := TIdURI.Create( URL );
+       uriList := TStringList.Create;
+       try
+               ExtractHttpFields(
+                       ['&'], [],
+                       Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+
+               postURL         := uri.Protocol + '://' + uri.Host + '/bbs/write.cgi';
+               postData        :=
+                       'NAME='                 + HttpEncode( inName ) +
+                       '&MAIL='                + HttpEncode( inMail ) +
+                       '&MESSAGE='     + HttpEncode( inMessage ) +
+                       '&BBS='                 + uriList.Values[ 'BBS' ] +
+                       '&KEY='                 + uriList.Values[ 'KEY' ] +
+                       '&TIME='                + IntToStr( DateTimeToUnix( Now ) ) +
+                       '&submit='      + HttpEncode( '\8f\91\82«\8d\9e\82Þ' );
+
+               // \93Æ\8e©\82É\92Ê\90M\82µ\82È\82¢\8fê\8d\87\82Í InternalPost \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
+               InternalPost( PChar( postURL ), PChar( postData ),PChar(URL), postResult );
+               DisposeResultString( postResult );
+
+               Result := dsComplete
+       finally
+               uri.Free;
+               uriList.Free;
        end;
 
 end;
@@ -391,20 +635,15 @@ begin
 
        // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
        // InternalAbon \82¨\82æ\82Ñ Dat2HTML \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
-       if FDat = nil then begin
-               if IsLogFile then begin
-                       // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
-                       FDat                    := TStringList.Create;
-                       FDat.LoadFromFile( FilePath );
-               end else begin
-                       // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
-                       Result := '';
-                       Exit;
-               end;
+       LoadDat;
+       if (FDat = nil) or (inNo - 1 < 0 ) or (inNo - 1 >= FDat.Count) then begin
+               // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
+               Result := '';
+               Exit;
        end;
        res                     := FDat[ inNo - 1 ];
-       tmp                     := InternalAbon( PChar( res ) );
-       try
+       tmp                     := InternalAbonForOne( PChar( res ), PChar(FilePath), inNo);
+    try
                Result  := Dat2HTML( string( tmp ), inNo );
        finally
                DisposeResultString( tmp );
@@ -413,6 +652,35 @@ begin
 end;
 
 // *************************************************************************
+// \83\8c\83X\94Ô\8d\86 inNo \82É\91Î\82·\82é Dat \82ð\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+function TMachiBBSThreadItem.GetDat(
+       inNo            : Integer               // \97v\8b\81\82³\82ê\82½\83\8c\83X\94Ô\8d\86
+) : string;                                            // \82Q\82¿\82á\82ñ\82Ë\82é\82ÌDat\8c`\8e®
+var
+       //res: string;
+       tmp: PChar;
+begin
+       //Result        := '';
+       // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
+       LoadDat;
+       if (FDat = nil) or (inNo - 1 < 0 ) or (inNo - 1 >= FDat.Count)  then begin
+               // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
+               tmp := CreateResultString('');
+               Result := tmp;
+               DisposeResultString(tmp);
+               Exit;
+       end;
+       tmp := CreateResultString(FDat[ inNo - 1]);
+       try
+               Result := string(tmp);
+       finally
+               DisposeResultString(tmp);
+       end;
+
+end;
+
+// *************************************************************************
 // \83X\83\8c\83b\83h\82Ì\83w\83b\83_ html \82ð\97v\8b\81\82³\82ê\82½
 // *************************************************************************
 function TMachiBBSThreadItem.GetHeader(
@@ -428,17 +696,10 @@ begin
 
 
        // GetRes \82ð\8cÄ\82Î\82ê\82é\82±\82Æ\82ª\97\\91z\82³\82ê\82é\82Ì\82Å FDat \82ð\90\90¬\82µ\82Ä\82¨\82­
-       if FDat <> nil then begin
-               try
-                       FDat.Free;
-                       FDat := nil;
-               except
-               end;
-       end;
-       if IsLogFile then begin
-               // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
-               FDat                    := TStringList.Create;
-               FDat.LoadFromFile( FilePath );
+       try
+               FreeDat;
+               LoadDat;
+       except
        end;
 
 end;
@@ -457,60 +718,126 @@ begin
 
        // \82à\82¤ GetRes \82Í\8cÄ\82Î\82ê\82È\82¢\82Æ\8ev\82¤\82Ì\82Å FDat \82ð\8aJ\95ú\82µ\82Ä\82¨\82­
        try
-               if FDat <> nil then begin
-                       FDat.Free;
-                       FDat := nil;
-               end;
+               FreeDat;
        except
        end;
 
 end;
 
 // *************************************************************************
+// \82±\82Ì ThreadItem \82ª\91®\82·\82é\94Â\82Ì URL \82ð\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+function       TMachiBBSThreadItem.GetBoardURL : string;
+var
+       uri                                             : TIdURI;
+       uriList                         : TStringList;
+       tmp: PChar;
+begin
+    tmp := nil;
+       if Copy( URL, Length( URL ), 1 ) = '/' then
+               uri := TIdURI.Create( URL )
+       else
+               uri := TIdURI.Create( URL + '/' );
+       uriList := TStringList.Create;
+       try
+               ExtractHttpFields(
+                       ['&'], [],
+                       Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+               FileName := uriList.Values[ 'KEY' ] + '.dat';
+               // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446
+               // http://hokkaido.machi.to/hokkaidou/
+               tmp             := CreateResultString(
+                       uri.Protocol + '://' + uri.Host + '/' + uriList.Values[ 'BBS' ] + '/' );
+               Result := string(tmp);
+       finally
+               DisposeResultString(tmp);
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+
+// *************************************************************************
 // \82Ü\82¿BBS\82Ì HTML \82ð 2ch \82Ì dat \8c`\8e®\82É
 // *************************************************************************
 procedure      TMachiBBSThreadItem.To2chDat(
-       ioHTML                  : TStringList
+       ioHTML                          : TStringList;
+       inStartNo                       : Integer = 1
 );
 var
-       i, bound                : Integer;
-       foundPos                : Integer;
-       strTmp                  : string;
-       res                                     : TStringList;
+       i, bound                        : Integer;
+       foundPos,foundPos2                      : Integer;
+       strTmp                          : string;
+       res                                             : TStringList;
+       no                                              : Integer;
 const
-       MAIL_TAG                = '<a href="mailto:';
+       MAIL_TAG                        = '<a href="mailto:';
 begin
 
        //===== 2ch \82Ì dat \8c`\8e®\82É\95Ï\8a·
        // \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­
-       ioHTML.Text     := StringReplace( ioHTML.Text, #13#10, '', [rfReplaceAll] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, #13#10, '');
+       //StringReplace( ioHTML.Text, #13#10, '', [rfReplaceAll] );
        // \91ã\82í\82è\82É <dt> \82ð\8ds\82Ì\8bæ\90Ø\82è\82É\82·\82é
-       ioHTML.Text     := StringReplace( ioHTML.Text, '<dt>', #10, [rfReplaceAll] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<dt>', #10 );
+       //StringReplace( ioHTML.Text, '<dt>', #10, [rfReplaceAll] );
        // <dt> \82©\82ç\8en\82Ü\82Á\82Ä\82¢\82é\82Ì\82Å\8dÅ\8f\89\82Í\8bó\82Ì\82Í\82¸
        if Length( ioHTML[ 0 ] ) = 0 then
                ioHTML.Delete( 0 );
+
+       // \8cy\82­\82 \82Ú\81[\82ñ\83`\83F\83b\83N
+       // \81¦\91å\8eG\94c\82¾\82©\82ç\82¿\82á\82ñ\82Æ\8fo\97\88\82Ä\82È\82¢\82©\82à
+       try
+               i                       := 0;
+               while i < ioHTML.Count do begin
+                       foundPos := AnsiPos( ' ', ioHTML[ i ] );
+                       if foundPos > 0 then begin
+                               no := StrToInt( Copy( ioHTML[ i ], 1, foundPos - 1 ) );
+                               if inStartNo < no then
+                                       ioHTML.Insert( i, '<><><><>' );
+                       end;
+                       Inc( i );
+                       Inc( inStartNo );
+               end;
+       except
+               // \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Å
+       end;
+
+
        // \83g\83\8a\83b\83v\82Ì\8cã\82Ì '<b> </b>' \82ð\8bó\82É
-       ioHTML.Text     := StringReplace( ioHTML.Text, '<b> </b>', '', [rfReplaceAll, rfIgnoreCase] );
+    if AnsiPos('\81\9f</b>', ioHTML.Text) <> 0 then begin
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<b> </b></font>', '</b></font>', true );
+        ioHTML.Text    := CustomStringReplace( ioHTML.Text, '<b> </B></a>', '</b></a>', true );
+    end;
+       //ioHTML.Text   := CustomStringReplace( ioHTML.Text, '<b> </b>', '', true );
+       //StringReplace( ioHTML.Text, '<b> </b>', '', [rfReplaceAll, rfIgnoreCase] );
        // '<b>' \82Í\83\81\81[\83\8b\82Æ\96¼\91O\82Ì\8bæ\90Ø\82è
-       ioHTML.Text     := StringReplace( ioHTML.Text, '<b>', '<>', [rfReplaceAll, rfIgnoreCase] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<b>', '<>', true );
+       //StringReplace( ioHTML.Text, '<b>', '<>', [rfReplaceAll, rfIgnoreCase] );
        // \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É
-       ioHTML.Text     := StringReplace( ioHTML.Text, '</b></a>', '<>', [rfReplaceAll, rfIgnoreCase] );
-       ioHTML.Text     := StringReplace( ioHTML.Text, '</b>', '<>', [rfReplaceAll, rfIgnoreCase] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '</b></a>', '<>', true );
+       //StringReplace( ioHTML.Text, '</b></a>', '<>', [rfReplaceAll, rfIgnoreCase] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '</b>', '<>', true );
+    ioHTML.Text        := CustomStringReplace( ioHTML.Text, '\81\9f<>', '\81\9f</b>', true );
+       //StringReplace( ioHTML.Text, '</b>', '<>', [rfReplaceAll, rfIgnoreCase] );
        // '<dd>' \82ð\96{\95\82Æ\82Ì\8bæ\90Ø\82è\82É
-       ioHTML.Text     := StringReplace( ioHTML.Text, '<dd>', '<>', [rfReplaceAll, rfIgnoreCase] );
+       ioHTML.Text     := CustomStringReplace( ioHTML.Text, '<dd>', '<>', true );
+       //StringReplace( ioHTML.Text, '<dd>', '<>', [rfReplaceAll, rfIgnoreCase] );
 
        res := TStringList.Create;
        try
                bound := ioHTML.Count - 1;
                for i := 0 to bound do begin
-                       res.Text := StringReplace( ioHTML[ i ], '<>', #10, [rfReplaceAll] );
+            // \83X\83N\83\8a\83v\83g\82ª\8aÜ\82Ü\82ê\82Ä\82¢\82½\82ç\8dí\8f\9c\82·\82é\81i\8dL\8d\90\91Î\8dô\81j
+                       res.Text := CustomStringReplace( ioHTML[ i ], '<>', #10 );
+                                               //StringReplace( ioHTML[ i ], '<>', #10, [rfReplaceAll] );
                        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ß
-                               foundPos := Pos( MAIL_TAG, res[ 0 ] );
+                               foundPos := AnsiPos( MAIL_TAG, res[ 0 ] );
                                if foundPos > 0 then begin
                                        // \83\81\81[\83\8b\83A\83h\83\8c\83X\82ð\94²\82«\8fo\82·
                                        foundPos        := foundPos + Length( MAIL_TAG );
                                        res[ 0 ]        := Copy( res[ 0 ], foundPos, Length( res[ 0 ] ) );
-                                       strTmp          := Copy( res[ 0 ], 1, Pos( '">', res[ 0 ] ) - 1 );
+                                       strTmp          := Copy( res[ 0 ], 1, AnsiPos( '">', res[ 0 ] ) - 1 );
                                        // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82µ\82Ä\96ß\82·
                                        res[ 0 ]        := res[ 1 ];
                                        res[ 1 ]        := strTmp;
@@ -519,10 +846,25 @@ begin
                                        res[ 0 ]        := res[ 1 ];
                                        res[ 1 ]        := '';
                                end;
-                               res[ 2 ] := StringReplace( res[ 2 ], '[', 'ID:', [] );
+                               res[ 2 ] := StringReplace( res[ 2 ], '[', 'IP:', [] );
                                res[ 2 ] := StringReplace( res[ 2 ], ']', '', [] );
+
+                if AnsiPos('</font> \93\8a\8de\93ú\81F', res[ 2 ]) = 1 then begin
+                       res[ 2 ] := StringReplace( res[ 2 ], '</font> \93\8a\8de\93ú\81F', '', [] );
+                end else if AnsiPos(' \93\8a\8de\93ú\81F', res[ 2 ]) = 1 then begin
+                    res[ 2 ] := StringReplace( res[ 2 ], ' \93\8a\8de\93ú\81F', '', [] );
+                end;
                        end;
-                       ioHTML[ i ] := StringReplace( res.Text, #13#10, '<>', [rfReplaceAll] );
+                       ioHTML[ i ] := CustomStringReplace( res.Text, #13#10, '<>');
+            // \8dL\8d\90\83X\83N\83\8a\83v\83g\91Î\8dô
+            foundPos := Pos( '<script', ioHTML[ i ] );
+            if foundPos > 0 then begin
+                foundPos2 := Pos( '</script>', ioHTML[ i ] );
+                if (foundPos2 > foundPos) then begin
+                    ioHTML[ i ] := Copy(ioHTML[ i ], 1, foundPos-1) +
+                                   Copy(ioHTML[ i ], foundPos2 + 9, Length(ioHTML[ i ]));
+                end;
+            end;
                end;
        finally
                res.Free;
@@ -531,6 +873,83 @@ begin
 end;
 
 // *************************************************************************
+// FDat \82Ì\90\90¬
+// *************************************************************************
+procedure      TMachiBBSThreadItem.LoadDat;
+begin
+
+       if FDat = nil then begin
+               if IsLogFile then begin
+                       // dat \82Ì\93Ç\82Ý\8d\9e\82Ý
+                       FDat := TStringList.Create;
+                       FDat.LoadFromFile( FilePath );
+               end;
+       end;
+
+end;
+
+// *************************************************************************
+// FDat \82Ì\8aJ\95ú
+// *************************************************************************
+procedure      TMachiBBSThreadItem.FreeDat;
+begin
+
+       if FDat <> nil then begin
+               FDat.Free;
+               FDat := nil;
+       end;
+
+end;
+
+// *************************************************************************
+// \88À\91S\82È( '/' \82Å\8fI\82í\82é )\93Ç\82Ý\8d\9e\82Ý\82Ì URL
+// *************************************************************************
+function       TMachiBBSThreadItem.ReadURL : string;
+var
+       uri                             : TIdURI;
+       uriList         : TStringList;
+       foundPos        : Integer;
+const
+    THREAD_MARK2= '/bbs/read.cgi';
+begin
+
+       foundPos := AnsiPos( '?', URL );
+       if foundPos > 0 then begin
+               uri := TIdURI.Create( URL );
+               uriList := TStringList.Create;
+               try
+                       ExtractHttpFields( ['&'], [], Copy( URL, foundPos + 1, MaxInt ), uriList );
+                       Result :=
+                               uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
+                               'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ];
+               finally
+                       uri.Free;
+                       uriList.Free;
+               end;
+       end else begin
+        // \90V\8c`\8e® ?
+        foundPos := AnsiPos(THREAD_MARK2, URL);
+       if (foundPos > 0) then begin
+            uri := TIdURI.Create( URL );
+            uriList := TStringList.Create;
+            try
+                uriList.Delimiter := '/';
+                uriList.DelimitedText  := uri.Path;
+                if (uriList.Count >= 5) then begin
+                           Result :=
+                                   uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
+                                   'BBS=' + uriList[3] + '&KEY=' + uriList[4];
+                end;
+            finally
+                       uri.Free;
+                       uriList.Free;
+            end;
+        end;
+    end;
+
+end;
+
+// *************************************************************************
 // TThreadItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\90\90¬\82·\82é)
 // *************************************************************************
 procedure ThreadItemOnCreateOfTMachiBBSThreadItem(
@@ -578,20 +997,19 @@ begin
        inherited;
 
        OnDownload                                              := Download;
+       OnCreateThread                          := CreateThread;
        OnEnumThread                                    := EnumThread;
        OnFileName2ThreadURL    := ToThreadURL;
 
        FilePath                        := '';
        FIsTemporary    := False;
        FDat                                    := nil;
+    Is2ch                      := False;
 
-       if Copy( URL, Length( URL ), 1 ) = '/' then
-               uri := TIdURI.Create( URL )
-       else
-               uri := TIdURI.Create( URL + '/' );
-       uriList := TStringList.Create;
+       uri                     := TIdURI.Create( SubjectURL );
+       uriList := TStringList.Create;
        try
-               YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+               ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
                // http://hokkaido.machi.to/hokkaidou/subject.txt
                FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
                IsLogFile       := FileExists( FilePath );
@@ -634,17 +1052,10 @@ var
        responseCode    : Longint;
        uri                                             : TIdURI;
        uriList                         : TStringList;
-const
-       SUBJECT_NAME    = 'subject.txt';
 begin
 
        Result := dsError;
 
-       if Copy( URL, Length( URL ), 1 ) = '/' then
-               uri := TIdURI.Create( URL + SUBJECT_NAME )
-       else
-               uri := TIdURI.Create( URL );
-       uriList := TStringList.Create;
        if FDat <> nil then begin
                try
                        FDat.Free;
@@ -652,16 +1063,18 @@ begin
                except
                end;
        end;
-       FDat := TStringList.Create;
+       FDat            := TStringList.Create;
+       uri                     := TIdURI.Create( SubjectURL );
+       uriList := TStringList.Create;
        // \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Í
        // InternalDownload \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
-       modified := LastModified;
-       responseCode := InternalDownload( PChar( uri.URI ), modified, downResult );
+       modified                        := LastModified;
+       responseCode    := InternalDownload( PChar( uri.URI ), modified, downResult );
        try
                if responseCode = 200 then begin
                        try
                                // \83p\83X\82ð\8eZ\8fo
-                               YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+                               ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
                                if MyLogFolder = '' then begin
                                        // \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
                                        FilePath                        := TemporaryFile;
@@ -677,6 +1090,11 @@ begin
                                FDat.Text := string( downResult );
                                // \95Û\91
                                FDat.SaveToFile( FilePath );
+
+                               IsLogFile                       := True;
+                               RoundDate                       := Now;
+                               LastModified    := modified;
+                               LastGetTime             := Now;
                        finally
                                uri.Free;
                                uriList.Free;
@@ -690,6 +1108,52 @@ begin
 end;
 
 // *************************************************************************
+// \83X\83\8c\97§\82Ä\82ð\8ew\8e¦\82³\82ê\82½
+// *************************************************************************
+function       TMachiBBSBoardItem.CreateThread(
+       inSubject                       : string;       // \83X\83\8c\83^\83C
+       inName                          : string;       // \96¼\91O(\83n\83\93\83h\83\8b)
+       inMail                          : string;       // \83\81\81[\83\8b\83A\83h\83\8c\83X
+       inMessage                       : string        // \96{\95
+) : TDownloadState;                            // \8f\91\82«\8d\9e\82Ý\82ª\90¬\8c÷\82µ\82½\82©\82Ç\82¤\82©
+var
+       postURL                         : string;
+       postData                        : string;
+       postResult              : PChar;
+       uri                                             : TIdURI;
+       uriList                         : TStringList;
+begin
+
+       uri                     := TIdURI.Create( URL );
+       uriList := TStringList.Create;
+       try
+               ExtractHttpFields(
+                       ['&'], [],
+                       Copy( uri.Params, AnsiPos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+
+               postURL         := uri.Protocol + '://' + uri.Host + '/bbs/write.cgi';
+               postData        :=
+                       'SUBJECT='      + HttpEncode( inSubject ) +
+                       '&NAME='                + HttpEncode( inName ) +
+                       '&MAIL='                + HttpEncode( inMail ) +
+                       '&MESSAGE='     + HttpEncode( inMessage ) +
+                       '&BBS='                 + uriList[ 1 ] +
+                       '&TIME='                + IntToStr( DateTimeToUnix( Now ) ) +
+                       '&submit='      + HttpEncode( '\90V\8bK\8f\91\82«\8d\9e\82Ý' );
+
+               // \93Æ\8e©\82É\92Ê\90M\82µ\82È\82¢\8fê\8d\87\82Í InternalPost \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
+               InternalPost( PChar( postURL ), PChar( postData ),PChar(URL), postResult );
+               DisposeResultString( postResult );
+
+               Result := dsComplete
+       finally
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+
+// *************************************************************************
 // \83X\83\8c\88ê\97\97\82Ì URL \82©\82ç\83X\83\8c\83b\83h\82Ì URL \82ð\93±\82«\8fo\82·
 // *************************************************************************
 function TMachiBBSBoardItem.ToThreadURL(
@@ -702,20 +1166,17 @@ var
        found                           : Integer;
 begin
 
-       found := Pos( '.', inFileName );
+       found := AnsiPos( '.', inFileName );
        if found > 0 then
                inFileName := Copy( inFileName, 1, found - 1 );
-       if Copy( URL, Length( URL ), 1 ) = '/' then
-               uri := TIdURI.Create( URL )
-       else
-               uri := TIdURI.Create( URL + '/' );
-       uriList := TStringList.Create;
 
+       uri                     := TIdURI.Create( SubjectURL );
+       uriList := TStringList.Create;
        try
                try
                        // http://hokkaido.machi.to/hokkaidou/
                        // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
-                       YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+                       ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
                        threadURL       := uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
                                'BBS=' + uriList[ 1 ] + '&KEY=' + inFileName + '&LAST=50';
                        Result          := threadURL;
@@ -738,22 +1199,17 @@ procedure        TMachiBBSBoardItem.EnumThread(
 var
        uri                                     : TIdURI;
        uriList                 : TStringList;
-const
-       SUBJECT_NAME    = 'subject.txt';
 begin
 
        try
                if FDat = nil then begin
                        FDat := TStringList.Create;
 
-                       if Copy( URL, Length( URL ), 1 ) = '/' then
-                               uri := TIdURI.Create( URL + SUBJECT_NAME )
-                       else
-                               uri := TIdURI.Create( URL );
-                       uriList := TStringList.Create;
+                       uri                     := TIdURI.Create( SubjectURL );
+                       uriList := TStringList.Create;
                        try
                                // \83p\83X\82ð\8eZ\8fo
-                               YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+                               ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
                                // http://hokkaido.machi.to/hokkaidou/subject.txt
                                FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
                                if FileExists( FilePath ) then
@@ -766,13 +1222,41 @@ begin
                end;
 
                // \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é
-               inherited EnumThread( inCallBack, FDat.Text );
+               inherited EnumThread( inCallBack, CustomStringReplace( FDat.Text, ',', '<>' ) );
        except
        end;
 
 end;
 
 // *************************************************************************
+// \83X\83\8c\88ê\97\97\82Ì URL \82ð\8b\81\82ß\82é
+// *************************************************************************
+function       TMachiBBSBoardItem.SubjectURL : string;
+var
+       uri                     : TIdURI;
+       uriList : TStringList;
+begin
+
+       uri                     := TIdURI.Create( URL );
+       uriList := TStringList.Create;
+       try
+               if uri.Document <> SUBJECT_NAME then begin
+                       if Copy( URL, Length( URL ), 1 ) = '/' then
+                               Result := URL + SUBJECT_NAME
+                       else
+                               Result := URL + '/' + SUBJECT_NAME;
+               end else begin
+                       // \82±\82±\82É\82Í\97\88\82È\82¢\82Æ\8ev\82¤\82¯\82Ç
+                       Result := URL;
+               end;
+       finally
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+
+// *************************************************************************
 // TBoardItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\90\90¬\82·\82é)
 // *************************************************************************
 procedure BoardItemOnCreateOfTMachiBBSBoardItem(
@@ -845,8 +1329,8 @@ end;
 
 exports
        OnVersionInfo,
-       OnAcceptURL;
-
+       OnAcceptURL,
+    OnExtractBoardURL;
 begin
 
        try