OSDN Git Service

・まちBBSを実装。
authoryoffy <yoffy>
Sat, 15 Nov 2003 16:00:15 +0000 (16:00 +0000)
committeryoffy <yoffy>
Sat, 15 Nov 2003 16:00:15 +0000 (16:00 +0000)
res/ExternalBoardPlugIn/MachiBBSPlugIn.dpr [new file with mode: 0644]
res/ExternalBoardPlugIn/MachiBBSPlugIn.res [new file with mode: 0644]

diff --git a/res/ExternalBoardPlugIn/MachiBBSPlugIn.dpr b/res/ExternalBoardPlugIn/MachiBBSPlugIn.dpr
new file mode 100644 (file)
index 0000000..73ce931
--- /dev/null
@@ -0,0 +1,957 @@
+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,
+       IdURI,
+       YofUtils in '..\..\YofUtils.pas',
+       PlugInMain in 'PlugInMain.pas',
+       ThreadItem in 'ThreadItem.pas',
+       BoardItem in 'BoardItem.pas',
+       FilePath in 'FilePath.pas';
+
+{$R *.res}
+
+type
+       // =========================================================================
+       // TMachiBBSThreadItem
+       // =========================================================================
+       TMachiBBSThreadItem = class(TThreadItem)
+       private
+               FIsTemporary    : Boolean;
+               FDat                                    : TStringList;
+
+       public
+               constructor     Create( inInstance : DWORD );
+               destructor      Destroy; override;
+
+       private
+               function        Download : TDownloadState;
+               function        GetRes( inNo : Integer ) : string;
+               function        GetHeader( inOptionalHeader : string ) : string;
+               function        GetFooter( inOptionalFooter : string ) : string;
+
+               procedure       To2chDat( ioHTML : TStringList );
+       end;
+
+       // =========================================================================
+       // TMachiBBSBoardItem
+       // =========================================================================
+       TMachiBBSBoardItem = class(TBoardItem)
+       private
+               FIsTemporary    : Boolean;
+               FDat                                    : TStringList;
+
+       public
+               constructor     Create( inInstance : DWORD );
+               destructor      Destroy; override;
+
+       private
+               function        Download : TDownloadState;
+               function        ToThreadURL( inFileName : string ) : string;
+               procedure       EnumThread( inCallBack : TBoardItemEnumThreadCallBack );
+
+       end;
+
+       // =========================================================================
+       // \83T\83u\83W\83F\83N\83g\83\8c\83R\81[\83h
+       // =========================================================================
+       TSubjectRec = record
+               FFileName: string;
+               FTitle: string;
+               FCount: Integer;
+       end;
+
+const
+       LOG_DIR                                         = 'MachiBBS\';
+
+       PLUGIN_NAME                             = 'MachiBBSPlugIn';
+       MAJOR_VERSION                   = 1;
+       MINOR_VERSION                   = 0;
+       RELEASE_VERSION         = 'develop';
+       REVISION_VERSION        = 1;
+
+// =========================================================================
+// \8eG\97p\8aÖ\90\94
+// =========================================================================
+
+// *************************************************************************
+// \83e\83\93\83|\83\89\83\8a\82È\83p\83X\82Ì\8eæ\93¾
+// *************************************************************************
+function TemporaryFile : string;
+var
+       tempPath : array [0..MAX_PATH] of       char;
+begin
+
+       GetTempPath( SizeOf(tempPath), tempPath );
+       repeat
+               Result := tempPath + IntToStr( Random( $7fffffff ) );
+       until not FileExists( Result );
+
+end;
+
+// *************************************************************************
+// \82Ü\82¿BBS\97p\83\8d\83O\83t\83H\83\8b\83_\8eæ\93¾
+// *************************************************************************
+function MyLogFolder : string;
+var
+       folder : string;
+begin
+
+       folder := LogFolder;
+       if Length( folder ) = 0 then
+               Result := ''
+       else
+               Result := folder + LOG_DIR;
+
+end;
+
+(*************************************************************************
+ *\83f\83B\83\8c\83N\83g\83\8a\82ª\91\8dÝ\82·\82é\82©\83`\83F\83b\83N
+ *************************************************************************)
+function DirectoryExistsEx(const Name: string): Boolean;
+var
+       Code: Integer;
+begin
+       Code := GetFileAttributes(PChar(Name));
+       Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
+end;
+
+(*************************************************************************
+ *\83f\83B\83\8c\83N\83g\83\8a\8dì\90¬\81i\95¡\90\94\8aK\91w\91Î\89\9e\81j
+ *************************************************************************)
+function ForceDirectoriesEx(Dir: string): Boolean;
+begin
+       Result := True;
+       if Length(Dir) = 0 then
+               raise Exception.Create('\83t\83H\83\8b\83_\82ª\8dì\90¬\8fo\97\88\82Ü\82¹\82ñ');
+       Dir := ExcludeTrailingPathDelimiter(Dir);
+       if (Length(Dir) < 3) or DirectoryExistsEx(Dir)
+               or (ExtractFilePath(Dir) = Dir) then Exit; // avoid 'xyz:\' problem.
+       Result := ForceDirectoriesEx(ExtractFilePath(Dir)) and CreateDir(Dir);
+end;
+
+(*************************************************************************
+ *\82Ç\82±\82©\82Ì\83T\83C\83g\82©\82ç\82Ì\83p\83N\83\8a
+ *************************************************************************)
+function RemoveToken(var s: string; delimiter: string): string;
+var
+       p: Integer;
+begin
+       p := AnsiPos(delimiter, s);
+       if p = 0 then
+               Result := s
+       else
+               Result := Copy(s, 1, p - 1);
+       s := Copy(s, Length(Result) + Length(delimiter) + 1, Length(s));
+end;
+
+(*************************************************************************
+ *\82Ç\82±\82©\82Ì\83T\83C\83g\82©\82ç\82Ì\83p\83N\83\8a
+ *************************************************************************)
+function GetTokenIndex(s: string; delimiter: string; index: Integer): string;
+var
+       i: Integer;
+begin
+       Result := '';
+       for i := 0 to index do
+               Result := RemoveToken(s, delimiter);
+end;
+
+(*************************************************************************
+ *\95\8e\9a\97ñ\90\94\8e\9a\83`\83F\83b\83N
+ *************************************************************************)
+function IsNumeric(s: string): boolean;
+var
+       e: integer;
+       v: integer;
+begin
+       Val(s, v, e);
+       Result := e = 0;
+end;
+
+(*************************************************************************
+ *\83T\83u\83W\83F\83N\83g\88ê\8ds\82ð\95ª\8a\84
+ *************************************************************************)
+function DivideSubject(Line: string): TSubjectRec;
+var
+       i: integer;
+       ws: WideString;
+       Delim: string;
+       LeftK: string;
+       RightK: string;
+begin
+       Result.FCount := 0;
+
+       if Pos('<>', Line) = 0 then
+               Delim := ','
+       else
+               Delim := '<>';
+
+       Result.FFileName := GetTokenIndex(Line, Delim, 0);
+       Result.FTitle := GetTokenIndex(Line, Delim, 1);
+
+       ws := Trim(Result.FTitle);
+
+       if Copy(ws, Length(ws), 1) = ')' then begin
+               LeftK := '(';
+               RightK := ')';
+       end else if Copy(ws, Length(ws), 1) = '\81j' then begin
+               LeftK := '\81i';
+               RightK := '\81j';
+       end else if Copy(ws, Length(ws), 1) = '<' then begin
+               LeftK := '<';
+               RightK := '>';
+       end;
+
+       for i := Length(ws) - 1 downto 1 do begin
+               if ws[i] = LeftK then begin
+                       ws := Copy(ws, i + 1, Length(ws) - i - 1);
+                       if IsNumeric(ws) then
+                               Result.FCount := StrToInt(ws);
+                       Result.FTitle := Trim(StringReplace(Result.FTitle, LeftK + ws + RightK, '', [rfReplaceAll]));
+                       break;
+               end;
+       end;
+end;
+
+
+
+// =========================================================================
+// PlugIn
+// =========================================================================
+
+// *************************************************************************
+// \83v\83\89\83O\83C\83\93\82Ì\83o\81[\83W\83\87\83\93\82ð\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+procedure OnVersionInfo(
+       var outAgent            : PChar;        // \83o\81[\83W\83\87\83\93\82ð\88ê\90Ø\8aÜ\82Ü\82È\82¢\8f\83\90\88\82È\96¼\8fÌ
+       var outMajor            : DWORD;        // \83\81\83W\83\83\81[\83o\81[\83W\83\87\83\93
+       var outMinor            : DWORD;        // \83}\83C\83i\81[\83o\81[\83W\83\87\83\93
+       var outRelease  : PChar;        // \83\8a\83\8a\81[\83X\92i\8aK\96¼
+       var outRevision : DWORD         // \83\8a\83r\83W\83\87\83\93\83i\83\93\83o\81[
+); stdcall;
+begin
+
+       try
+               outAgent                := CreateResultString( PChar( PLUGIN_NAME ) );
+               outMajor                := MAJOR_VERSION;
+               outMinor                := MINOR_VERSION;
+               outRelease      := CreateResultString( PChar( RELEASE_VERSION ) );
+               outRevision     := REVISION_VERSION;
+       except
+               outAgent                := nil;
+               outMajor                := 0;
+               outMinor                := 0;
+               outRelease      := nil;
+               outRevision     := 0;
+       end;
+
+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
+var
+       URI : TIdURI;
+       foundPos : Integer;
+const
+       BBS_HOST = 'machi.to';
+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 );
+               try
+                       foundPos := Pos( BBS_HOST, URI.Host );
+                       Result := (foundPos > 0) and (Length( URI.Host ) - foundPos + 1 = Length( BBS_HOST ))
+               finally
+                       URI.Free;
+               end;
+       except
+               Result := False;
+       end;
+
+end;
+
+
+
+// =========================================================================
+// TMachiBBSThreadItem
+// =========================================================================
+
+// *************************************************************************
+// \83R\83\93\83X\83g\83\89\83N\83^
+// *************************************************************************
+constructor TMachiBBSThreadItem.Create(
+       inInstance      : DWORD
+);
+var
+       uri                                     : TIdURI;
+       uriList                 : TStringList;
+begin
+
+       inherited;
+
+       OnDownload              := Download;
+       OnGetRes                        := GetRes;
+       OnGetHeader             := GetHeader;
+       OnGetFooter             := GetFooter;
+
+       FilePath                        := '';
+       FIsTemporary    := False;
+       FDat                                    := nil;
+
+       uri                     := TIdURI.Create( URL );
+       uriList := TStringList.Create;
+       try
+               // http://hokkaido.machi.to/bbs/read.pl?BBS=hokkaidou&KEY=1061764446&LAST=50
+               YofUtils.ExtractHttpFields(
+                       ['&'], [],
+                       Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), uriList );
+               FileName        := uriList.Values[ 'KEY' ] + '.dat';
+               FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
+               IsLogFile       := FileExists( FilePath );
+       finally
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+
+// *************************************************************************
+// \83f\83X\83g\83\89\83N\83^
+// *************************************************************************
+destructor TMachiBBSThreadItem.Destroy;
+begin
+
+       if FDat <> nil then begin
+               try
+                       FDat.Free;
+                       FDat := nil;
+               except
+               end;
+       end;
+
+       // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
+       if FIsTemporary then
+               DeleteFile( FilePath );
+
+       inherited;
+
+end;
+
+// *************************************************************************
+// \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½
+// *************************************************************************
+function TMachiBBSThreadItem.Download : TDownloadState;
+var
+       modified                        : Double;
+       tmp                                             : PChar;
+       downResult              : TStringList;
+       responseCode    : Longint;
+       logStream                       : TFileStream;
+       uri                                             : TIdURI;
+       uriList                         : TStringList;
+       datURL                          : string;
+       foundPos                        : Integer;
+begin
+
+       Result := dsError;
+
+       uri                     := TIdURI.Create( URL );
+       uriList := TStringList.Create;
+       try
+               YofUtils.ExtractHttpFields(
+                       ['&'], [],
+                       Copy( uri.Params, Pos( '?', uri.Params ) + 1, Length( uri.Params ) ), 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;
+                       FIsTemporary    := True;
+               end else begin
+                       FilePath        := MyLogFolder + uriList.Values[ 'BBS' ] + '\' + uriList.Values[ 'KEY' ] + '.dat';
+                       FIsTemporary    := False;
+               end;
+
+               // \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 )
+               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
+                       // \90V\92\85\82Ì\82Ý\8eæ\93¾\82·\82é\95û\96@\82Í\96³\82µ
+                       datURL          :=
+                               uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
+                               'BBS=' + uriList.Values[ 'BBS' ] + '&KEY=' + uriList.Values[ 'KEY' ];
+                       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 downResult.Count > 0 then begin
+                                                               logStream.Position      := 0;
+                                                               logStream.Write( PChar( downResult.Text )^, Length( downResult.Text ) );
+                                                               logStream.Size := Length( downResult.Text );
+
+                                                               if AllResCount <> downResult.Count then begin
+                                                                       if AllResCount = 0 then
+                                                                               Result := dsComplete
+                                                                       else
+                                                                               Result := dsDiffComplete;
+                                                                       LastModified                            := modified;
+                                                                       IsLogFile                                               := True;
+                                                                       NewReceive                                      := Count + 1;
+                                                                       NewResCount                                     := downResult.Count - Count;
+                                                                       Count                                                           := downResult.Count;
+                                                               end else begin
+                                                                       Result                                                  := dsNotModify;
+                                                               end;
+                                                       end;
+                                               end;
+                                       finally
+                                               downResult.Free;
+                                       end;
+                               end;
+                       finally
+                               DisposeResultString( tmp );
+                       end;
+               finally
+                       logStream.Free;
+               end;
+       finally
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+
+// *************************************************************************
+// \83\8c\83X\94Ô\8d\86 inNo \82É\91Î\82·\82é html \82ð\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+function TMachiBBSThreadItem.GetRes(
+       inNo            : Integer               // \97v\8b\81\82³\82ê\82½\83\8c\83X\94Ô\8d\86
+) : string;                                            // \91Î\89\9e\82·\82é HTML
+var
+       res                     : string;
+       tmp                     : PChar;
+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 );
+
+                       AllResCount := FDat.Count; // \96{\93\96\82Í\82±\82±\82Å\82â\82é\82×\82«\82Å\82Í\82È\82¢\82ª\8eÀ\8c±\82Ì\82½\82ß\82Ì\88ê\8e\9e\93I\82È\82à\82Ì
+               end else begin
+                       // \83\8d\83O\82É\91\8dÝ\82µ\82È\82¢\82Ì\82Å\82±\82Ì\82Ü\82Ü\8fI\97¹
+                       Result := '';
+                       Exit;
+               end;
+       end;
+       res                     := FDat[ inNo - 1 ];
+       tmp                     := InternalAbon( PChar( res ) );
+       try
+               Result  := Dat2HTML( string( tmp ), inNo );
+       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(
+       inOptionalHeader        : string
+) : string;
+begin
+
+       // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
+       // InternalHeader \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
+       Result := InternalHeader(
+               '<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">' +
+               inOptionalHeader );
+
+
+       // 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 );
+       end;
+
+end;
+
+// *************************************************************************
+// \83X\83\8c\83b\83h\82Ì\83t\83b\83^ html \82ð\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+function TMachiBBSThreadItem.GetFooter(
+       inOptionalFooter : string
+) : string;
+begin
+
+       // \93Æ\8e©\82É\83t\83B\83\8b\83^\83\8a\83\93\83O\82ð\8ds\82í\82È\82¢\8fê\8d\87\82Í
+       // InternalFooter \82É\94C\82¹\82é\82±\82Æ\82ª\8fo\97\88\82é
+       Result := InternalFooter( inOptionalFooter );
+
+       // \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;
+       except
+       end;
+
+end;
+
+// *************************************************************************
+// \82Ü\82¿BBS\82Ì HTML \82ð 2ch \82Ì dat \8c`\8e®\82É
+// *************************************************************************
+procedure      TMachiBBSThreadItem.To2chDat(
+       ioHTML                  : TStringList
+);
+var
+       i, bound                : Integer;
+       foundPos                : Integer;
+       strTmp                  : string;
+       res                                     : TStringList;
+const
+       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] );
+       // \91ã\82í\82è\82É <dt> \82ð\8ds\82Ì\8bæ\90Ø\82è\82É\82·\82é
+       ioHTML.Text     := 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 );
+       // \83g\83\8a\83b\83v\82Ì\8cã\82Ì '<b> </b>' \82ð\8bó\82É
+       ioHTML.Text     := 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] );
+       // \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] );
+       // '<dd>' \82ð\96{\95\82Æ\82Ì\8bæ\90Ø\82è\82É
+       ioHTML.Text     := 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] );
+                       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 ] );
+                               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 );
+                                       // \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;
+                               end else begin
+                                       // \83\81\81[\83\8b\82Æ\96¼\91O\82ª\8bt\82È\82Ì\82Å\82Ð\82Á\82­\82è\95Ô\82·
+                                       res[ 0 ]        := res[ 1 ];
+                                       res[ 1 ]        := '';
+                               end;
+                               res[ 2 ] := StringReplace( res[ 2 ], '[', 'ID:', [] );
+                               res[ 2 ] := StringReplace( res[ 2 ], ']', '', [] );
+                       end;
+                       ioHTML[ i ] := StringReplace( res.Text, #13#10, '<>', [rfReplaceAll] );
+               end;
+       finally
+               res.Free;
+       end;
+
+end;
+
+// *************************************************************************
+// TThreadItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\90\90¬\82·\82é)
+// *************************************************************************
+procedure ThreadItemOnCreateOfTMachiBBSThreadItem(
+       inInstance : DWORD
+);
+var
+       threadItem : TMachiBBSThreadItem;
+begin
+
+       threadItem := TMachiBBSThreadItem.Create( inInstance );
+       ThreadItemSetLong( inInstance, tipContext, DWORD( threadItem ) );
+
+end;
+
+// *************************************************************************
+// TThreadItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSThreadItem \82ð\94j\8aü\82·\82é)
+// *************************************************************************
+procedure ThreadItemOnDisposeOfTMachiBBSThreadItem(
+       inInstance : DWORD
+);
+var
+       threadItem : TMachiBBSThreadItem;
+begin
+
+       threadItem := TMachiBBSThreadItem( ThreadItemGetLong( inInstance, tipContext ) );
+       threadItem.Free;
+
+end;
+
+// =========================================================================
+// TMachiBBSBoardItem
+// =========================================================================
+
+// *************************************************************************
+// \83R\83\93\83X\83g\83\89\83N\83^
+// *************************************************************************
+constructor TMachiBBSBoardItem.Create(
+       inInstance      : DWORD
+);
+var
+       uri                                     : TIdURI;
+       uriList                 : TStringList;
+begin
+
+       inherited;
+
+       OnDownload                                              := Download;
+       OnEnumThread                                    := EnumThread;
+       OnFileName2ThreadURL    := ToThreadURL;
+
+       FilePath                        := '';
+       FIsTemporary    := False;
+       FDat                                    := nil;
+
+       if Copy( URL, Length( URL ), 1 ) = '/' then
+               uri := TIdURI.Create( URL )
+       else
+               uri := TIdURI.Create( URL + '/' );
+       uriList := TStringList.Create;
+       try
+               YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+               // http://hokkaido.machi.to/hokkaidou/subject.txt
+               FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
+               IsLogFile       := FileExists( FilePath );
+       finally
+               uri.Free;
+               uriList.Free;
+       end;
+
+end;
+// *************************************************************************
+// \83f\83X\83g\83\89\83N\83^
+// *************************************************************************
+destructor TMachiBBSBoardItem.Destroy;
+begin
+
+       if FDat <> nil then begin
+               try
+                       FDat.Free;
+                       FDat := nil;
+               except
+               end;
+       end;
+
+       // \88ê\8e\9e\83t\83@\83C\83\8b\82Ì\8fê\8d\87\82Í\8dí\8f\9c\82·\82é
+       if FIsTemporary then
+               DeleteFile( FilePath );
+
+       inherited;
+
+end;
+
+// *************************************************************************
+// \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½
+// *************************************************************************
+function TMachiBBSBoardItem.Download : TDownloadState;
+var
+       modified                        : Double;
+       downResult              : PChar;
+       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;
+                       FDat := nil;
+               except
+               end;
+       end;
+       FDat := 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 );
+       try
+               if responseCode = 200 then begin
+                       try
+                               // \83p\83X\82ð\8eZ\8fo
+                               YofUtils.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;
+                                       FIsTemporary    := True;
+                               end else begin
+                                       FilePath                        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
+                                       FIsTemporary    := False
+                               end;
+
+                               // \95Û\91\97p\82Ì\83f\83B\83\8c\83N\83g\83\8a\82ð\8c@\82é
+                               ForceDirectoriesEx( Copy( FilePath, 1, LastDelimiter( '\', FilePath ) ) );
+
+                               FDat.Text := string( downResult );
+                               // \95Û\91
+                               FDat.SaveToFile( FilePath );
+                       finally
+                               uri.Free;
+                               uriList.Free;
+                       end;
+                       Result := dsComplete;
+               end;
+       finally
+               DisposeResultString( downResult );
+       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(
+       inFileName      : string        // \83X\83\8c\83b\83h\83t\83@\83C\83\8b\96¼
+) : string;                                                    // \83X\83\8c\83b\83h\82Ì URL
+var
+       threadURL               : string;
+       uri                                     : TIdURI;
+       uriList                 : TStringList;
+       found                           : Integer;
+begin
+
+       found := Pos( '.', 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;
+
+       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 );
+                       threadURL       := uri.Protocol + '://' + uri.Host + '/bbs/read.pl?' +
+                               'BBS=' + uriList[ 1 ] + '&KEY=' + inFileName + '&LAST=50';
+                       Result          := threadURL;
+               finally
+                       uri.Free;
+                       uriList.Free;
+               end;
+       except
+               Result := '';
+       end;
+
+end;
+
+// *************************************************************************
+// \82±\82Ì\94Â\82É\82¢\82­\82Â\82Ì\83X\83\8c\82ª\82 \82é\82©\97v\8b\81\82³\82ê\82½
+// *************************************************************************
+procedure      TMachiBBSBoardItem.EnumThread(
+       inCallBack      : TBoardItemEnumThreadCallBack
+);
+var
+       uri                                     : TIdURI;
+       uriList                 : TStringList;
+       rec                                     : TSubjectRec;
+       i                                               : Integer;
+       isContinue      : Boolean;
+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;
+                       try
+                               // \83p\83X\82ð\8eZ\8fo
+                               YofUtils.ExtractHttpFields( ['/', '?'], [], uri.Path, uriList );
+                               // http://hokkaido.machi.to/hokkaidou/subject.txt
+                               FilePath        := MyLogFolder + uriList[ 1 ] + '\' + uri.Document;
+                               if FileExists( FilePath ) then
+                                       // \93Ç\82Ý\8d\9e\82Ý
+                                       FDat.LoadFromFile( FilePath );
+                       finally
+                               uri.Free;
+                               uriList.Free;
+                       end;
+               end;
+
+               for i := 0 to FDat.Count - 1 do begin
+                       rec                                             := DivideSubject( FDat[i] );
+                       rec.FFileName   := Trim(rec.FFileName);
+                       if (rec.FTitle = '') and (rec.FCount = 0) then
+                               Continue;
+
+                       isContinue := inCallBack(
+                               Instance,
+                               PChar( ToThreadURL( rec.FFileName ) ),
+                               PChar( rec.FTitle ),
+                               DWORD( rec.FCount ) );
+
+                       if ( not isContinue ) then
+                               Break;
+               end;
+       except
+       end;
+
+end;
+
+// *************************************************************************
+// TBoardItem \82ª\90\90¬\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\90\90¬\82·\82é)
+// *************************************************************************
+procedure BoardItemOnCreateOfTMachiBBSBoardItem(
+       inInstance : DWORD
+);
+var
+       boardItem : TMachiBBSBoardItem;
+begin
+
+       boardItem := TMachiBBSBoardItem.Create( inInstance );
+       BoardItemSetLong( inInstance, bipContext, DWORD( boardItem ) );
+
+end;
+
+// *************************************************************************
+// TBoardItem \82ª\94j\8aü\82³\82ê\82½\8fê\8d\87\82Ì\8f\88\92u(TMachiBBSBoardItem \82ð\94j\8aü\82·\82é)
+// *************************************************************************
+procedure BoardItemOnDisposeOfTMachiBBSBoardItem(
+       inInstance : DWORD
+);
+var
+       boardItem : TMachiBBSBoardItem;
+begin
+
+       boardItem := TMachiBBSBoardItem( BoardItemGetLong( inInstance, bipContext ) );
+       boardItem.Free;
+
+end;
+
+
+
+// =========================================================================
+// \83G\83\93\83g\83\8a\83|\83C\83\93\83g
+// =========================================================================
+procedure DLLEntry(
+       ul_reason_for_call : DWORD
+);
+var
+       module : HMODULE;
+begin
+
+       case ul_reason_for_call of
+               DLL_PROCESS_ATTACH:
+               begin
+                       Randomize;
+
+                       module := GetModuleHandle( nil );
+
+                       LoadInternalAPI( module );
+                       LoadInternalFilePathAPI( module );
+                       LoadInternalThreadItemAPI( module );
+                       LoadInternalBoardItemAPI( module );
+
+                       // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TThreadItem \82©\82ç TMachiBBSThreadItem \82É\95Ï\8dX\82·\82é
+                       ThreadItemOnCreate      := ThreadItemOnCreateOfTMachiBBSThreadItem;
+                       ThreadItemOnDispose     := ThreadItemOnDisposeOfTMachiBBSThreadItem;
+                       // ===== \83C\83\93\83X\83^\83\93\83X\82Ì\8eæ\82è\88µ\82¢\82ð TBoardItem \82©\82ç TMachiBBSBoardItem \82É\95Ï\8dX\82·\82é
+                       BoardItemOnCreate               := BoardItemOnCreateOfTMachiBBSBoardItem;
+                       BoardItemOnDispose      := BoardItemOnDisposeOfTMachiBBSBoardItem;
+               end;
+               DLL_PROCESS_DETACH:
+                       ;
+               DLL_THREAD_ATTACH:
+                       ;
+               DLL_THREAD_DETACH:
+                       ;
+       end;
+
+end;
+
+exports
+       OnVersionInfo,
+       OnAcceptURL;
+
+begin
+
+       try
+               DllProc := @DLLEntry;
+               DLLEntry( DLL_PROCESS_ATTACH );
+       except end;
+
+end.
diff --git a/res/ExternalBoardPlugIn/MachiBBSPlugIn.res b/res/ExternalBoardPlugIn/MachiBBSPlugIn.res
new file mode 100644 (file)
index 0000000..90e5abf
Binary files /dev/null and b/res/ExternalBoardPlugIn/MachiBBSPlugIn.res differ