--- /dev/null
+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.