unit uAPIDA; interface uses Classes, SysUtils, System.JSON, IdHTTP, IdSSLOpenSSL, flog; type TAPIClient = class(TObject) private FHttpClient: TIdHTTP; FSSLHandler: TIdSSLIOHandlerSocketOpenSSL; FToken: string; procedure SetToken(const Value: string); procedure CheckHTTPError(AResponseCode: Integer; const AResponse: string); public constructor Create; destructor Destroy; override; function GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string; function GetUserInfo: TJSONObject; function SubscribeToChannel(const uid, clientUID: string): TJSONObject; property Token: string read FToken write SetToken; end; implementation const ContentType = 'application/x-www-form-urlencoded'; UserAgent = 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; AuthorizationHeader = 'Authorization: Bearer '; constructor TAPIClient.Create; begin inherited; FHttpClient := TIdHTTP.Create(nil); // создаём SSL handler без владельца — явное управление FSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); FSSLHandler.SSLOptions.Method := sslvSSLv23; FHttpClient.IOHandler := FSSLHandler; FHttpClient.Request.UserAgent := UserAgent; FHttpClient.Request.ContentType := ContentType; FHttpClient.HandleRedirects := True; end; destructor TAPIClient.Destroy; begin // Отключаем и освобождаем в безопасном порядке try if Assigned(FHttpClient) then begin try // если нужно — прервать активные соединения except end; end; finally // Сначала освобождаем IOHandler (если он не принадлежит FHttpClient) FreeAndNil(FSSLHandler); FreeAndNil(FHttpClient); inherited; end; end; procedure TAPIClient.CheckHTTPError(AResponseCode: Integer; const AResponse: string); begin if AResponseCode <> 200 then raise Exception.CreateFmt('HTTP Error %d: %s', [AResponseCode, AResponse]); end; function TAPIClient.GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string; var Response: string; Stream: TStringStream; Json: TJSONObject; begin Stream := TStringStream.Create( Format('grant_type=authorization_code&client_id=%s&client_secret=%s&redirect_uri=%s&code=%s', [client_id, client_secret, redirect_uri, code]), TEncoding.UTF8); try Response := FHttpClient.Post('https://www.donationalerts.com/oauth/token', Stream); CheckHTTPError(FHttpClient.ResponseCode, Response); Json := TJSONObject.ParseJSONValue(Response) as TJSONObject; try Result := Json.GetValue('access_token'); FToken := Result; finally Json.Free; end; finally Stream.Free; end; end; function TAPIClient.GetUserInfo: TJSONObject; var Response: string; begin FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken); try Response := FHttpClient.Get('https://www.donationalerts.com/api/v1/user/oauth'); CheckHTTPError(FHttpClient.ResponseCode, Response); Result := TJSONObject.ParseJSONValue(Response) as TJSONObject; finally FHttpClient.Request.CustomHeaders.Clear; end; end; function TAPIClient.SubscribeToChannel(const uid, clientUID: string): TJSONObject; var Response: string; Stream: TStringStream; RequestJSON: string; begin RequestJSON := Format('{"channels":["$alerts:donation_%s"], "client":"%s"}', [uid, clientUID]); Stream := TStringStream.Create(RequestJSON, TEncoding.UTF8); try FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken); FHttpClient.Request.ContentType := 'application/json'; Response := FHttpClient.Post('https://www.donationalerts.com/api/v1/centrifuge/subscribe', Stream); CheckHTTPError(FHttpClient.ResponseCode, Response); Result := TJSONObject.ParseJSONValue(Response) as TJSONObject; finally Stream.Free; FHttpClient.Request.CustomHeaders.Clear; end; end; procedure TAPIClient.SetToken(const Value: string); begin FToken := Value; end; end.