-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgocomicsapi.pas
270 lines (236 loc) · 8.91 KB
/
gocomicsapi.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
unit GoComicsAPI;
{$mode objfpc}{$H+}
interface
uses
SysUtils, Classes, fphttpclient, opensslsockets, LazFileUtils, strutils,
FPImage, FPReadJPEG, FPReadPNG, FPReadGIF;
const
BASE_URL = 'https://www.gocomics.com';
type
EInvalidDateError = class(Exception);
EInvalidEndpointError = class(Exception);
{ TGoComics }
TGoComics = class
private
FEndpoint: string;
FTitle: string;
FStartDate: TDateTime;
FPrevComicUrl: string;
FNextComicUrl: string;
FFirstComicUrl: string;
FLastComicUrl: string;
FPrevComicDate: TDateTime;
FNextComicDate: TDateTime;
FFirstComicDate: TDateTime;
FLastComicDate: TDateTime;
HTTPClient: TFPHTTPClient;
function GetStartDate: TDateTime;
function GetTitle: string;
function FormatDate(const ADate: TDateTime): string;
function ExtractImageUrlFromHtml(const Html: string): string;
function ExtractLatestComicUrlFromHtml(const Html: string): string;
procedure ExtractNavigationUrlsFromHtml(const Html: string);
public
constructor Create(const AEndpoint: string);
destructor Destroy; override;
function GetImageUrl(const ADate: TDateTime; out FileName: string; out ContentType: string): TMemoryStream;
function GetLatestComicUrl: string;
property StartDate: TDateTime read GetStartDate;
property Title: string read GetTitle;
property PrevComicUrl: string read FPrevComicUrl write FPrevComicUrl;
property NextComicUrl: string read FNextComicUrl write FNextComicUrl;
property FirstComicUrl: string read FFirstComicUrl write FFirstComicUrl;
property LastComicUrl: string read FLastComicUrl write FLastComicUrl;
property PrevComicDate: TDateTime read FPrevComicDate;
property NextComicDate: TDateTime read FNextComicDate;
property FirstComicDate: TDateTime read FFirstComicDate;
property LastComicDate: TDateTime read FLastComicDate;
end;
implementation
{ TGoComics }
function TGoComics.ExtractImageUrlFromHtml(const Html: string): string;
var
ImgPos, SrcPos: Integer;
ImgTag, SrcUrl: string;
begin
Result := '';
ImgPos := Pos('<img', Html);
while ImgPos > 0 do
begin
ImgTag := Copy(Html, ImgPos, PosEx('>', Html, ImgPos) - ImgPos + 1);
SrcPos := Pos('src="', ImgTag);
if SrcPos > 0 then
begin
SrcPos := SrcPos + Length('src="');
SrcUrl := Copy(ImgTag, SrcPos, PosEx('"', ImgTag, SrcPos) - SrcPos);
if Pos('assets.amuniversal.com', SrcUrl) > 0 then
begin
Result := SrcUrl;
Break;
end;
end;
ImgPos := PosEx('<img', Html, ImgPos + Length('<img'));
end;
end;
function TGoComics.ExtractLatestComicUrlFromHtml(const Html: string): string;
var
Pos1, Pos2: Integer;
begin
Pos1 := Pos('<div class="gc-deck gc-deck--cta-0">', Html);
if Pos1 > 0 then
begin
Pos1 := PosEx('<a class="gc-blended-link gc-blended-link--primary"', Html, Pos1);
if Pos1 > 0 then
begin
Pos1 := PosEx('href="', Html, Pos1) + Length('href="');
Pos2 := PosEx('"', Html, Pos1);
Result := BASE_URL + Copy(Html, Pos1, Pos2 - Pos1);
end;
end;
if Result = '' then
raise Exception.Create('Latest comic URL not found.');
end;
procedure TGoComics.ExtractNavigationUrlsFromHtml(const Html: string);
var
NavPos, LinkPos, ClassPos: Integer;
Url, ClassStr: string;
YearStr, MonthStr, DayStr: string;
Year, Month, Day: Integer;
begin
NavPos := Pos('<nav class="gc-calendar-nav" role="group" aria-label="Date Navigation Controls">', Html);
if NavPos > 0 then
begin
// Reset URLs
FFirstComicUrl := '';
FPrevComicUrl := '';
FNextComicUrl := '';
FLastComicUrl := '';
LinkPos := PosEx('<a role=''button'' href=''', Html, NavPos);
while LinkPos > 0 do
begin
LinkPos := LinkPos + Length('<a role=''button'' href=''''');
Url := Copy(Html, LinkPos, PosEx('''', Html, LinkPos) - LinkPos);
ClassPos := PosEx('class=''fa ', Html, LinkPos);
if ClassPos > 0 then
begin
ClassStr := Copy(Html, ClassPos + Length('class='''), PosEx('''', Html, ClassPos + Length('class=''')) - (ClassPos + Length('class=''')));
if Pos('fa-backward', ClassStr) > 0 then
if Url = ' class=' then begin FFirstComicUrl := '' end else begin FFirstComicUrl := BASE_URL + Url end
else if Pos('fa-caret-left', ClassStr) > 0 then
if Url = ' class=' then begin FPrevComicUrl := '' end else begin FPrevComicUrl := BASE_URL + Url end
//FPrevComicUrl := BASE_URL + Url
else if Pos('fa-caret-right', ClassStr) > 0 then
if Url = ' class=' then begin FNextComicUrl := '' end else begin FNextComicUrl := BASE_URL + Url end
//FNextComicUrl := BASE_URL + Url
else if Pos('fa-forward', ClassStr) > 0 then
if Url = ' class=' then begin FLastComicUrl := '' end else begin FLastComicUrl := BASE_URL + Url; end
//FLastComicUrl := BASE_URL + Url;
end;
LinkPos := PosEx('<a role=''button'' href=''', Html, LinkPos);
end;
// Extract dates for URLs
if FFirstComicUrl <> '' then
begin
YearStr := Copy(FFirstComicUrl, Length(FFirstComicUrl) - 9, 4);
MonthStr := Copy(FFirstComicUrl, Length(FFirstComicUrl) - 4, 2);
DayStr := Copy(FFirstComicUrl, Length(FFirstComicUrl) - 1, 2);
if TryStrToInt(YearStr, Year) and TryStrToInt(MonthStr, Month) and TryStrToInt(DayStr, Day) then
FFirstComicDate := EncodeDate(Year, Month, Day);
end;
if FPrevComicUrl <> '' then
begin
YearStr := Copy(FPrevComicUrl, Length(FPrevComicUrl) - 9, 4);
MonthStr := Copy(FPrevComicUrl, Length(FPrevComicUrl) - 4, 2);
DayStr := Copy(FPrevComicUrl, Length(FPrevComicUrl) - 1, 2);
if TryStrToInt(YearStr, Year) and TryStrToInt(MonthStr, Month) and TryStrToInt(DayStr, Day) then
FPrevComicDate := EncodeDate(Year, Month, Day);
end;
if FNextComicUrl <> '' then
begin
YearStr := Copy(FNextComicUrl, Length(FNextComicUrl) - 9, 4);
MonthStr := Copy(FNextComicUrl, Length(FNextComicUrl) - 4, 2);
DayStr := Copy(FNextComicUrl, Length(FNextComicUrl) - 1, 2);
if TryStrToInt(YearStr, Year) and TryStrToInt(MonthStr, Month) and TryStrToInt(DayStr, Day) then
FNextComicDate := EncodeDate(Year, Month, Day);
end;
if FLastComicUrl <> '' then
begin
YearStr := Copy(FLastComicUrl, Length(FLastComicUrl) - 9, 4);
MonthStr := Copy(FLastComicUrl, Length(FLastComicUrl) - 4, 2);
DayStr := Copy(FLastComicUrl, Length(FLastComicUrl) - 1, 2);
if TryStrToInt(YearStr, Year) and TryStrToInt(MonthStr, Month) and TryStrToInt(DayStr, Day) then
FLastComicDate := EncodeDate(Year, Month, Day);
end;
end;
end;
function TGoComics.GetStartDate: TDateTime;
begin
Result := FStartDate;
end;
function TGoComics.GetTitle: string;
begin
Result := FTitle;
end;
function TGoComics.FormatDate(const ADate: TDateTime): string;
begin
Result := FormatDateTime('yyyy"/"mm"/"dd', ADate);
end;
constructor TGoComics.Create(const AEndpoint: string);
begin
FEndpoint := AEndpoint;
HTTPClient := TFPHTTPClient.Create(nil);
// Simulated start date for the comic; this should be replaced with actual data if available
FStartDate := EncodeDate(2000, 1, 1);
end;
destructor TGoComics.Destroy;
begin
HTTPClient.Free;
inherited Destroy;
end;
function TGoComics.GetImageUrl(const ADate: TDateTime; out FileName: string; out ContentType: string): TMemoryStream;
var
URL, formattedDate, ComicImg: string;
Response: TStringStream;
begin
formattedDate := FormatDate(ADate);
URL := Format('%s/%s/%s', [BASE_URL, FEndpoint, formattedDate]);
Response := TStringStream.Create('');
Result := TMemoryStream.Create;
try
HTTPClient.AllowRedirect := True;
HTTPClient.Get(URL, Response); // Get the HTML page
ComicImg := ExtractImageUrlFromHtml(Response.DataString); // Extract the image URL from HTML
if ComicImg = '' then
raise Exception.Create('Comic image URL not found in the HTML response.');
ExtractNavigationUrlsFromHtml(Response.DataString); // Extract navigation URLs
// Download the comic image
HTTPClient.Get(ComicImg, Result); // Get the image directly into the stream
Result.Position := 0;
ContentType := HTTPClient.ResponseHeaders.Values['Content-Type']; // Extract content type
FileName := ExtractFileName(ComicImg); // Use the extracted file name
except
Result.Free;
raise;
end;
Response.Free;
end;
function TGoComics.GetLatestComicUrl: string;
var
URL, ResponseStr: string;
Response: TStringStream;
begin
URL := Format('%s/%s', [BASE_URL, FEndpoint]);
Response := TStringStream.Create('');
try
HTTPClient.Get(URL, Response);
ResponseStr := Response.DataString;
Result := ExtractLatestComicUrlFromHtml(ResponseStr);
finally
Response.Free;
end;
end;
initialization
ImageHandlers.RegisterImageReader('JPEG Image', 'jpg', TFPReaderJPEG);
ImageHandlers.RegisterImageReader('PNG Image', 'png', TFPReaderPNG);
ImageHandlers.RegisterImageReader('GIF Image', 'gif', TFPReaderGIF);
end.