OSDN Git Service

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