diff --git a/TTW_Bot_app.dpr b/TTW_Bot_app.dpr index f5acd68..1ca6deb 100644 --- a/TTW_Bot_app.dpr +++ b/TTW_Bot_app.dpr @@ -1,42 +1,49 @@ program TTW_Bot_app; uses - System.StartUpCopy, + System.StartUpCopy, SysUtils, FMX.Forms, - uGeneral in 'uGeneral.pas' {TTW_Bot}, - fSettings in 'fSettings.pas' {frSettings: TFrame}, - fAI in 'fAI.pas' {frAI: TFrame}, - fNotify in 'fNotify.pas' {frNotify: TFrame}, - fAutoActions in 'fAutoActions.pas' {frAutoActions: TFrame}, - fOBS in 'fOBS.pas' {frOBS: TFrame}, - fLog in 'fLog.pas' {frLog: TFrame}, + uGeneral in 'uGeneral.pas' {TTW_Bot} , + fSettings in 'fSettings.pas' {frSettings: TFrame} , + fAI in 'fAI.pas' {frAI: TFrame} , + fNotify in 'fNotify.pas' {frNotify: TFrame} , + fAutoActions in 'fAutoActions.pas' {frAutoActions: TFrame} , + fOBS in 'fOBS.pas' {frOBS: TFrame} , + fLog in 'fLog.pas' {frLog: TFrame} , uRecords in 'uRecords.pas', - fCommands in 'fCommands.pas' {frCommands: TFrame}, + fCommands in 'fCommands.pas' {frCommands: TFrame} , uDataBase in 'uDataBase.pas', - fColorSettings in 'fColorSettings.pas' {frColorSettings: TFrame}, - uCreateChat in 'uCreateChat.pas' {fCreateChat}, - fFontSettings in 'fFontSettings.pas' {frFontSettings: TFrame}, - uCreateNotify in 'uCreateNotify.pas' {fCreateNotify}, + fColorSettings in 'fColorSettings.pas' {frColorSettings: TFrame} , + uCreateChat in 'uCreateChat.pas' {fCreateChat} , + fFontSettings in 'fFontSettings.pas' {frFontSettings: TFrame} , + uCreateNotify in 'uCreateNotify.pas' {fCreateNotify} , uTWAuth in 'uTWAuth.pas', uTTWAPI in 'uTTWAPI.pas', uAPIDA in 'uAPIDA.pas', - uShowText in 'uShowText.pas' {fShowText}, + uShowText in 'uShowText.pas' {fShowText} , uWSDA in 'uWSDA.pas', - uQ in 'uQ.pas' {frmQ}, - fSimpleGrid in 'fSimpleGrid.pas' {frSimpleGrid: TFrame}, - fContruct in 'fContruct.pas' {frContruct: TFrame}, - fGroupsRequest in 'fGroupsRequest.pas' {frGroupsRequest: TFrame}, + uQ in 'uQ.pas' {frmQ} , + fSimpleGrid in 'fSimpleGrid.pas' {frSimpleGrid: TFrame} , + fContruct in 'fContruct.pas' {frContruct: TFrame} , + fGroupsRequest in 'fGroupsRequest.pas' {frGroupsRequest: TFrame} , uMyTimer in 'uMyTimer.pas', uRegExpr in 'uRegExpr.pas'; {$R *.res} begin + +{$IFDEF DEBUG} + ReportMemoryLeaksOnShutdown := True; +{$ENDIF} Application.Initialize; + Application.CreateForm(TTTW_Bot, TTW_Bot); + Application.OnException := TTW_Bot.GlobalExceptionHandler; Application.CreateForm(TfCreateChat, fCreateChat); Application.CreateForm(TfCreateNotify, fCreateNotify); Application.CreateForm(TfShowText, fShowText); Application.CreateForm(TfrmQ, frmQ); Application.Run; + end. diff --git a/TTW_Bot_app.dproj b/TTW_Bot_app.dproj index 184bd12..f7271e7 100644 --- a/TTW_Bot_app.dproj +++ b/TTW_Bot_app.dproj @@ -286,6 +286,11 @@ false PerMonitorV2 + true + 1033 + TTW_Bot_app_Icon1.ico + ..\ttw_fmx_v9\fawico_44_2.png + ..\ttw_fmx_v9\fawico_150_2.png PerMonitorV2 @@ -298,6 +303,12 @@ PerMonitorV2 + true + 10 + 1 + 1 + 1049 + CompanyName=PTyTb;FileDescription=$(MSBuildProjectName);FileVersion=10.1.1.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=ru.ptytb.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= PerMonitorV2 @@ -308,88 +319,71 @@
TTW_Bot
- fmx
frSettings
- fmx TFrame
frAI
- fmx TFrame
frNotify
- fmx TFrame
frAutoActions
- fmx TFrame
frOBS
- fmx TFrame
frLog
- fmx TFrame
frCommands
- fmx TFrame
frColorSettings
- fmx TFrame
fCreateChat
- fmx
frFontSettings
- fmx TFrame
fCreateNotify
- fmx
fShowText
- fmx
frmQ
- fmx
frSimpleGrid
- fmx TFrame
frContruct
- fmx TFrame
frGroupsRequest
- fmx TFrame
@@ -415,6 +409,10 @@ TTW_Bot_app.dpr + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + @@ -432,6 +430,20 @@ true + + + Assets\ + Logo150x150.png + true + + + + + Assets\ + Logo44x44.png + true + + .\ diff --git a/TTW_Bot_app_Icon.ico b/TTW_Bot_app_Icon.ico new file mode 100644 index 0000000..da28655 Binary files /dev/null and b/TTW_Bot_app_Icon.ico differ diff --git a/TTW_Bot_app_Icon1.ico b/TTW_Bot_app_Icon1.ico new file mode 100644 index 0000000..dca514b Binary files /dev/null and b/TTW_Bot_app_Icon1.ico differ diff --git a/fAutoActions.pas b/fAutoActions.pas index e33e6ee..7aa3658 100644 --- a/fAutoActions.pas +++ b/fAutoActions.pas @@ -76,6 +76,7 @@ type listTimer: TArray; listBanWords: TArray; listCounters: TArray; + destructor Destroy; override; procedure UpdateGridFromArray; procedure initTimers; end; @@ -138,12 +139,16 @@ var rx: TRegExpr; begin rx := TRegExpr.Create; + try rx.InputString := edtBanWordsCheck.text; rx.Expression := edtBanWords.text; if rx.Exec then lBanWordsCheck.text := 'есть банворд' else lBanWordsCheck.text := 'нет банворда'; + finally + rx.Free; + end; end; procedure TfrAutoActions.btnBanWordsDelClick(Sender: TObject); @@ -322,7 +327,8 @@ begin if Assigned(FTimerList[SelectedRow]) then begin FTimerList[SelectedRow].StopT; - FTimerList[SelectedRow].TerminateAndDestroy; // Метод для остановки потока + FTimerList[SelectedRow].Terminate; // Метод для остановки потока + FTimerList[SelectedRow].Free; FTimerList.Delete(SelectedRow); // Удаляем из списка с автоуничтожением end; end; @@ -334,6 +340,26 @@ begin DB.SaveRecordArray('listTimer', listTimer); end; +destructor TfrAutoActions.Destroy; +var + i: Integer; +begin + if Assigned(FTimerList) then + begin + for i := FTimerList.Count - 1 downto 0 do + begin + if Assigned(FTimerList[i]) then + begin + FTimerList[i].StopT; + FTimerList[i].Terminate; + FTimerList[i].Free; + end; + end; + FreeAndNil(FTimerList); + end; + inherited; +end; + procedure TfrAutoActions.initTimers; var I: Integer; @@ -369,7 +395,6 @@ begin edtCounterName.text := sgCounter.Cells[0, Row]; edtCounterTrigger.text := sgCounter.Cells[1, Row]; edtCounterCount.text := sgCounter.Cells[2, Row]; - end; procedure TfrAutoActions.sgTimersCellClick(const Column: TColumn; diff --git a/fFontSettings.pas b/fFontSettings.pas index fa2f0ca..b44d70d 100644 --- a/fFontSettings.pas +++ b/fFontSettings.pas @@ -3,7 +3,8 @@ unit fFontSettings; interface uses - System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, FMX.ListBox, FMX.Colors, FMX.Edit, FMX.EditBox, FMX.SpinBox, FMX.Controls.Presentation; diff --git a/fLog.fmx b/fLog.fmx index a80085b..53ba1b8 100644 --- a/fLog.fmx +++ b/fLog.fmx @@ -14,6 +14,7 @@ object frLog: TfrLog TabOrder = 0 Text = #1054#1095#1080#1089#1090#1080#1090#1100 TextSettings.Trimming = None + OnClick = btnClearClick end object chkWARNING: TCheckBox Position.X = 160.000000000000000000 @@ -23,6 +24,7 @@ object frLog: TfrLog Size.PlatformDefault = False TabOrder = 1 Text = #1055#1088#1077#1076#1091#1087#1088#1077#1078#1076#1077#1085#1080#1103 + OnChange = chkWARNINGChange end object chkERROR: TCheckBox Position.X = 289.000000000000000000 @@ -32,12 +34,14 @@ object frLog: TfrLog Size.PlatformDefault = False TabOrder = 2 Text = #1054#1096#1080#1073#1082#1080 + OnChange = chkWARNINGChange end object chkDEBUG: TCheckBox Position.X = 369.000000000000000000 Position.Y = 8.000000000000000000 TabOrder = 3 Text = #1054#1090#1083#1072#1076#1082#1072 + OnChange = chkWARNINGChange end object chkINFO: TCheckBox Position.X = 96.000000000000000000 @@ -47,6 +51,7 @@ object frLog: TfrLog Size.PlatformDefault = False TabOrder = 4 Text = #1048#1085#1092#1086 + OnChange = chkWARNINGChange end end object sgLog: TStringGrid @@ -58,8 +63,8 @@ object frLog: TfrLog Size.PlatformDefault = False TabOrder = 1 RowCount = 0 - Viewport.Width = 796.000000000000000000 - Viewport.Height = 477.000000000000000000 + Viewport.Width = 800.000000000000000000 + Viewport.Height = 502.000000000000000000 object StringColumn5: TStringColumn Header = #1044#1072#1090#1072 HeaderSettings.TextSettings.WordWrap = False diff --git a/fLog.pas b/fLog.pas index d22df5d..f5f6cdb 100644 --- a/fLog.pas +++ b/fLog.pas @@ -3,10 +3,12 @@ unit fLog; interface uses - System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, - System.Rtti, FMX.Grid.Style, FMX.Grid, FMX.ScrollBox, - FMX.Controls.Presentation; + System.Rtti, FMX.Grid.Style, System.Generics.Collections, FMX.Grid, + FMX.ScrollBox, + FMX.Controls.Presentation, uRecords; type TfrLog = class(TFrame) @@ -22,14 +24,71 @@ type StringColumn2: TStringColumn; StringColumn3: TStringColumn; StringColumn4: TStringColumn; + procedure btnClearClick(Sender: TObject); + procedure chkWARNINGChange(Sender: TObject); private { Private declarations } public { Public declarations } + FLogList: TList; + destructor Destroy; override; + procedure UpdateGridFilters; end; implementation {$R *.fmx} +{ TfrLog } + +procedure TfrLog.chkWARNINGChange(Sender: TObject); +begin + UpdateGridFilters; +end; + +destructor TfrLog.Destroy; +begin + FLogList.Free; + inherited; +end; + +procedure TfrLog.btnClearClick(Sender: TObject); +begin + FLogList.Clear; + UpdateGridFilters; +end; + +procedure TfrLog.UpdateGridFilters; +var + ml: TRLog; + NewRow: integer; +begin + sgLog.BeginUpdate; + try + sgLog.RowCount := 0; + // Проверка наличия всех колонок + if sgLog.ColumnCount < 5 then + raise Exception.Create('Грид должен содержать 5 колонок.'); + for ml in FLogList do + begin + // Проверка фильтра + if ((ml.rType = 'WARNING') and chkWARNING.IsChecked) or + ((ml.rType = 'ERROR') and chkERROR.IsChecked) or + ((ml.rType = 'DEBUG') and chkDEBUG.IsChecked) or + ((ml.rType = 'INFO') and chkINFO.IsChecked) then + begin + sgLog.RowCount := sgLog.RowCount + 1; + NewRow := sgLog.RowCount - 1; + // Заполнение данных с проверкой колонок + sgLog.Cells[0, NewRow] := TimeToStr(ml.rTime); // Колонка 0 + sgLog.Cells[1, NewRow] := ml.rType; // Колонка 1 + sgLog.Cells[2, NewRow] := ml.rModule; // Колонка 2 + sgLog.Cells[3, NewRow] := ml.rMethod; // Колонка 3 + sgLog.Cells[4, NewRow] := ml.rMessage; // Колонка 4 + end; + end; + finally + sgLog.EndUpdate; + end; +end; end. diff --git a/fOBS.pas b/fOBS.pas index 502232b..ffa60f3 100644 --- a/fOBS.pas +++ b/fOBS.pas @@ -175,7 +175,7 @@ begin if listKandinsky[i].port = aPort then begin // Сдвигаем элементы массива - for j := i to High(listKandinsky) do + for j := i to High(listKandinsky) - 1 do listKandinsky[j] := listKandinsky[j + 1]; // Уменьшаем размер массива SetLength(listKandinsky, Length(listKandinsky) - 1); @@ -197,7 +197,7 @@ begin if listNotify[i].port = aPort then begin // Сдвигаем элементы массива - for j := i to High(listNotify) do + for j := i to High(listNotify) - 1 do listNotify[j] := listNotify[j + 1]; // Уменьшаем размер массива SetLength(listNotify, Length(listNotify) - 1); diff --git a/fSettings.fmx b/fSettings.fmx index 9584a3c..d8bd93b 100644 --- a/fSettings.fmx +++ b/fSettings.fmx @@ -134,7 +134,7 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 34 + TabOrder = 33 Text = #1054#1090#1082#1088#1099#1090#1100' '#1089#1090#1088#1080#1084 TextSettings.Trimming = None OnClick = btnOpenStreamClick @@ -145,14 +145,14 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 35 + TabOrder = 34 Text = #1055#1086#1083#1091#1095#1080#1090#1100' Token' TextSettings.Trimming = None OnClick = btnGetTokenStreamerClick end object edtBotTokenStreamer: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 36 + TabOrder = 35 Password = True Position.X = 8.000000000000000000 Position.Y = 146.000000000000000000 @@ -191,7 +191,7 @@ object frSettings: TfrSettings object btnDAGetCode: TButton Position.X = 200.000000000000000000 Position.Y = 216.000000000000000000 - TabOrder = 45 + TabOrder = 43 Text = #1055#1086#1083#1091#1095#1080#1090#1100 TextSettings.Trimming = None OnClick = btnDAGetCodeClick @@ -201,11 +201,11 @@ object frSettings: TfrSettings Position.Y = 24.000000000000000000 TextSettings.Trimming = None Text = 'Client ID' - TabOrder = 36 + TabOrder = 35 end object edtDAClientID: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 40 + TabOrder = 39 Password = True Position.X = 8.000000000000000000 Position.Y = 49.000000000000000000 @@ -218,11 +218,11 @@ object frSettings: TfrSettings Position.Y = 79.000000000000000000 TextSettings.Trimming = None Text = 'Client Secret' - TabOrder = 38 + TabOrder = 36 end object edtDAClientSecret: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 39 + TabOrder = 38 Password = True Position.X = 8.000000000000000000 Position.Y = 104.000000000000000000 @@ -235,11 +235,11 @@ object frSettings: TfrSettings Position.Y = 134.000000000000000000 TextSettings.Trimming = None Text = 'Redirect URL' - TabOrder = 41 + TabOrder = 40 end object edtDARedirectURL: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 42 + TabOrder = 41 Password = True Position.X = 8.000000000000000000 Position.Y = 159.000000000000000000 @@ -249,7 +249,7 @@ object frSettings: TfrSettings end object edtDACode: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 43 + TabOrder = 42 Password = True Position.X = 8.000000000000000000 Position.Y = 214.000000000000000000 @@ -270,7 +270,7 @@ object frSettings: TfrSettings Size.Width = 121.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 46 + TabOrder = 45 Text = #1055#1086#1076#1082#1083#1102#1095#1080#1090#1100#1089#1103 TextSettings.Trimming = None OnClick = btnDAStartClick diff --git a/fSettings.pas b/fSettings.pas index 87f3d34..f2bfcec 100644 --- a/fSettings.pas +++ b/fSettings.pas @@ -6,9 +6,9 @@ uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, uQ, FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, - ShellAPI, system.IOUtils, uDataBase, + ShellAPI, System.IOUtils, uDataBase, FMX.Controls.Presentation, FMX.Edit, uTWAuth, uRecords, uAPIDA, uShowText, - json, uWSDA; + json, uWSDA, fLog; type TfrSettings = class(TFrame) @@ -68,7 +68,9 @@ type public { Public declarations } FWSClient: TWSClient; + destructor Destroy; override; procedure init(); + end; implementation @@ -92,6 +94,7 @@ begin twa := TTTWAuth.Create; twa.OnToken := OnTokenDA; twa.StartServer(Url); + // ttw_Auth будет освобожден автоматически после получения токена (см. uTWAuth) end; procedure TfrSettings.OnTokenDA(txt: string); @@ -103,6 +106,7 @@ begin end; end; + procedure TfrSettings.btnDAStartClick(Sender: TObject); var UserInfo: TJSONObject; @@ -110,33 +114,84 @@ var begin if btnDAStart.text = 'Подключиться' then begin - try // Получение токена - if FAPIClient.Token = '' then - begin - FAPIClient.Token := FAPIClient.GetAccessToken(edtDAClientID.text, - edtDAClientSecret.text, edtDARedirectURL.text, edtDACode.text); - FWSClient.APIClient := FAPIClient; - UserInfo := FAPIClient.GetUserInfo; - Data := UserInfo.GetValue('data'); - FWSClient.Wsstoken := Data.GetValue('socket_connection_token'); - FWSClient.WSID := Data.GetValue('id'); + UserInfo := nil; + try + try + if not Assigned(FAPIClient) then + init; + + if FAPIClient.Token = '' then + begin + try + FAPIClient.Token := FAPIClient.GetAccessToken( + edtDAClientID.text, + edtDAClientSecret.text, + edtDARedirectURL.text, + edtDACode.text + ); + except + on E: Exception do + begin + TTW_Bot.toLog('fSettings', 'btnDAStartClick', 'Ошибка получения токена: ' + E.Message,2); + Exit; + end; + end; + + FWSClient.APIClient := FAPIClient; + + try + UserInfo := FAPIClient.GetUserInfo; + Data := UserInfo.GetValue('data'); + FWSClient.Wsstoken := Data.GetValue('socket_connection_token'); + FWSClient.WSID := Data.GetValue('id'); + except + on E: Exception do + begin + TTW_Bot.toLog( 'fSettings','btnDAStartClick', 'Ошибка получения UserInfo: ' + E.Message,2); + Exit; + end; + end; + end; + + try + FWSClient.Connect('wss://centrifugo.donationalerts.com/connection/websocket'); + FWSClient.Send(Format( + '{"params":{"token":"%s"},"id":1}', + [FWSClient.Wsstoken] + )); + except + on E: Exception do + TTW_Bot.toLog( 'fSettings','btnDAStartClick', 'Ошибка подключения к WebSocket: ' + E.Message,2); + end; + + except + on E: Exception do + TTW_Bot.toLog('fSettings', 'btnDAStartClick', 'Неизвестная ошибка: ' + E.Message,2); end; - // Получение информации о пользователе - - - FWSClient.Connect - ('wss://centrifugo.donationalerts.com/connection/websocket'); - FWSClient.Send(Format('{"params":{"token":"%s"},"id":1}', - [FWSClient.Wsstoken])); finally UserInfo.Free; end; end else begin - FWSClient.Disconnect; - btnDAStart.ImageIndex := 18; - btnDAStart.text := 'Подключиться'; + try + edtDACode.Text:=''; + if Assigned(FWSClient) then + begin + try + FWSClient.Disconnect; + except + on E: Exception do + TTW_Bot.toLog( 'fSettings', 'btnDAStartClick', 'Ошибка при отключении WS: ' + E.Message,2); + end; + FreeAndNil(FWSClient); + end; + + FreeAndNil(FAPIClient); + finally + btnDAStart.ImageIndex := 18; + btnDAStart.text := 'Подключиться'; + end; end; end; @@ -160,6 +215,7 @@ var begin ttw_Auth := TTTWAuth.Create; ttw_Auth.OnToken := OnTTWToken; + // ttw_Auth будет освобожден автоматически после получения токена (см. uTWAuth) sope := 'moderator:manage:shoutouts' + '+moderator:manage:announcements' + '+moderator:manage:banned_users' + '+moderator:manage:warnings' + '+moderator:read:followers' + '+channel:manage:raids' + @@ -246,7 +302,7 @@ end; procedure TfrSettings.btnOpenRomaningClick(Sender: TObject); begin - ShellExecute(0, 'open', pwidechar(ExtractFilePath(myConst.DBPath)), + ShellExecute(0, 'open', pwidechar(ExtractFilePath(myConst.DBPath)), nil, nil, 1); end; @@ -256,12 +312,30 @@ begin nil, nil, 1); end; +destructor TfrSettings.Destroy; +begin +if Assigned(FWSClient) then + begin + try + FWSClient.Disconnect; // если есть метод отключения + except end; + FreeAndNil(FWSClient); + end; + FreeAndNil(FAPIClient); + inherited; +end; + procedure TfrSettings.init; begin - FAPIClient := TAPIClient.Create; - FWSClient := TWSClient.Create; - FWSClient.OnStatus := HandleWSStatus; - FWSClient.OnDonate := HandleWSDonate; +if not Assigned(FAPIClient) then + FAPIClient := TAPIClient.Create; + if not Assigned(FWSClient) then + begin + FWSClient := TWSClient.Create; + FWSClient.OnStatus := HandleWSStatus; + FWSClient.OnDonate := HandleWSDonate; + FWSClient.OnLog := TTW_Bot.toLog; + end; end; procedure TfrSettings.OnTTWToken(txt: string); diff --git a/uAPIDA.pas b/uAPIDA.pas index 0b401bd..16f476e 100644 --- a/uAPIDA.pas +++ b/uAPIDA.pas @@ -24,8 +24,6 @@ type 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'; @@ -35,7 +33,8 @@ constructor TAPIClient.Create; begin inherited; FHttpClient := TIdHTTP.Create(nil); - FSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(FHttpClient); + // создаём SSL handler без владельца — явное управление + FSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); FSSLHandler.SSLOptions.Method := sslvSSLv23; FHttpClient.IOHandler := FSSLHandler; FHttpClient.Request.UserAgent := UserAgent; @@ -45,8 +44,21 @@ end; destructor TAPIClient.Destroy; begin - FHttpClient.Free; - inherited; + // Отключаем и освобождаем в безопасном порядке + 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); @@ -108,7 +120,6 @@ begin Response := FHttpClient.Post('https://www.donationalerts.com/api/v1/centrifuge/subscribe', Stream); CheckHTTPError(FHttpClient.ResponseCode, Response); Result := TJSONObject.ParseJSONValue(Response) as TJSONObject; - // .toLog(3,'uAPIDA','SubscribeToChannel',Result.ToJSON); finally Stream.Free; FHttpClient.Request.CustomHeaders.Clear; @@ -121,3 +132,4 @@ begin end; end. + diff --git a/uCreateChat.fmx b/uCreateChat.fmx index f01cd9d..10e2f44 100644 --- a/uCreateChat.fmx +++ b/uCreateChat.fmx @@ -1,7 +1,7 @@ object fCreateChat: TfCreateChat Left = 0 Top = 0 - Caption = 'Form2' + Caption = #1056#1077#1076#1072#1082#1090#1086#1088' '#1095#1072#1090#1086#1074 ClientHeight = 287 ClientWidth = 810 FormFactor.Width = 320 @@ -25,7 +25,7 @@ object fCreateChat: TfCreateChat Size.Height = 251.000000000000000000 Size.PlatformDefault = False inherited ccbStyleBorderColor: TColorComboBox - TabOrder = 32 + TabOrder = 31 end inherited Label40: TLabel TabOrder = 8 @@ -42,9 +42,6 @@ object fCreateChat: TfCreateChat inherited sbStyleBlockBorderSize: TSpinBox TabOrder = 37 end - inherited sbStyleBlockPadding: TSpinBox - TabOrder = 45 - end inherited Label1: TLabel TabOrder = 34 end @@ -73,6 +70,12 @@ object fCreateChat: TfCreateChat inherited Label41: TLabel TabOrder = 6 end + inherited ccbFontColor: TColorComboBox + TabOrder = 36 + end + inherited Label49: TLabel + TabOrder = 35 + end inherited Label46: TLabel TabOrder = 39 end diff --git a/uCreateNotify.fmx b/uCreateNotify.fmx index 4f18629..36b84bd 100644 --- a/uCreateNotify.fmx +++ b/uCreateNotify.fmx @@ -1,7 +1,7 @@ object fCreateNotify: TfCreateNotify Left = 0 Top = 0 - Caption = 'Form2' + Caption = #1056#1077#1076#1072#1082#1090#1086#1088' '#1086#1087#1086#1074#1077#1097#1077#1085#1080#1103 ClientHeight = 383 ClientWidth = 813 FormFactor.Width = 320 @@ -146,7 +146,7 @@ object fCreateNotify: TfCreateNotify Position.X = 8.000000000000000000 Position.Y = 135.000000000000000000 Text = #1057#1086#1073#1099#1090#1080#1077 - TabOrder = 51 + TabOrder = 42 end object cbEventsType: TComboBox Items.Strings = ( @@ -160,22 +160,25 @@ object fCreateNotify: TfCreateNotify Size.Width = 192.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 52 + TabOrder = 43 + OnChange = cbEventsTypeChange end object Label3: TLabel Position.X = 8.000000000000000000 Position.Y = 190.000000000000000000 Text = #1059#1089#1083#1086#1074#1080#1077' '#1089#1091#1084#1084#1099 - TabOrder = 53 + Visible = False + TabOrder = 44 end object edtIF: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 54 + TabOrder = 45 Position.X = 8.000000000000000000 Position.Y = 215.000000000000000000 Size.Width = 192.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False + Visible = False end end object btnCreateEvent: TButton @@ -224,8 +227,11 @@ object fCreateNotify: TfCreateNotify Size.Width = 241.000000000000000000 Size.Height = 115.000000000000000000 Size.PlatformDefault = False - inherited Label41: TLabel - TabOrder = 6 + inherited ccbFontColor: TColorComboBox + TabOrder = 36 + end + inherited Label49: TLabel + TabOrder = 35 end inherited Label46: TLabel TabOrder = 39 @@ -249,8 +255,11 @@ object fCreateNotify: TfCreateNotify Size.Width = 241.000000000000000000 Size.Height = 115.000000000000000000 Size.PlatformDefault = False - inherited Label41: TLabel - TabOrder = 6 + inherited ccbFontColor: TColorComboBox + TabOrder = 36 + end + inherited Label49: TLabel + TabOrder = 35 end inherited Label46: TLabel TabOrder = 38 diff --git a/uCreateNotify.pas b/uCreateNotify.pas index dc48e6f..ac877ae 100644 --- a/uCreateNotify.pas +++ b/uCreateNotify.pas @@ -38,6 +38,7 @@ type procedure FormCreate(Sender: TObject); procedure btnCreateEventClick(Sender: TObject); procedure FormShow(Sender: TObject); + procedure cbEventsTypeChange(Sender: TObject); private { Private declarations } function GetColorFromColorPanel(aColor: TAlphaColor): string; @@ -154,6 +155,12 @@ begin close; end; +procedure TfCreateNotify.cbEventsTypeChange(Sender: TObject); +begin + Label3.Visible := cbEventsType.ItemIndex = 4; + edtIF.Visible := cbEventsType.ItemIndex = 4; +end; + procedure TfCreateNotify.FormCreate(Sender: TObject); var i: integer; @@ -268,8 +275,7 @@ begin TCheckBox(c).IsChecked := DB.ReadSetting(TCheckBox(c).Name) = '1'; end; end; - var - n := 1; + var cDir := myconst.fontsPath; // Искать в папке с программой var @@ -284,7 +290,7 @@ begin frFontSettings2.cbFontStyleDefault.Items.Add(SearchRec.Name); frFontSettings3.cbFontStyleDefault.Items.Add(SearchRec.Name); - Inc(n); + end; until FindNext(SearchRec) <> 0; ChDir('..'); diff --git a/uDataBase.pas b/uDataBase.pas index 24517aa..656f0d8 100644 --- a/uDataBase.pas +++ b/uDataBase.pas @@ -445,7 +445,6 @@ begin if not CheckTableExists(TableName) then begin Context := TRttiContext.Create; - try RttiType := Context.GetType(RecordTypeInfo); FieldDefs := ''; for Field in RttiType.GetFields do @@ -455,14 +454,11 @@ begin FieldDefs := FieldDefs + Field.Name + ' ' + GetSQLType(Field); end; FConnection.ExecSQL(Format('CREATE TABLE %s (%s)', [TableName, FieldDefs])); - finally - Context.Free; - end; end else begin Context := TRttiContext.Create; - try + RttiType := Context.GetType(RecordTypeInfo); Query := TFDQuery.Create(nil); try @@ -478,9 +474,7 @@ begin finally Query.Free; end; - finally - Context.Free; - end; + end; end; @@ -507,7 +501,7 @@ begin EnsureTableForRecord(TableName, TypeInfo(T)); Context := TRttiContext.Create; - try + RttiType := Context.GetType(TypeInfo(T)); Fields := RttiType.GetFields; @@ -598,9 +592,7 @@ begin finally Query.Free; end; - finally - Context.Free; - end; + end; diff --git a/uGeneral.fmx b/uGeneral.fmx index 5a2daea..c4990ab 100644 --- a/uGeneral.fmx +++ b/uGeneral.fmx @@ -1,7 +1,7 @@ object TTW_Bot: TTTW_Bot Left = 480 Top = 0 - Caption = 'Form1' + Caption = 'TTW_Bot' ClientHeight = 886 ClientWidth = 970 Position = Designed @@ -9,6 +9,7 @@ object TTW_Bot: TTTW_Bot FormFactor.Height = 480 FormFactor.Devices = [Desktop] OnCreate = FormCreate + OnDestroy = FormDestroy Left = 480 DesignerMasterStyle = 0 object V: TTabControl @@ -117,12 +118,12 @@ object TTW_Bot: TTTW_Bot inherited btnDAStart: TButton Images = ImageList1 ImageIndex = 18 - TabOrder = 43 + TabOrder = 42 OnClick = frSettings1btnDAStartClick end inherited btnGetDADef: TButton Images = ImageList1 - TabOrder = 45 + TabOrder = 44 end end inherited btnOpenRomaning: TButton @@ -660,23 +661,31 @@ object TTW_Bot: TTTW_Bot end end inherited GroupBox17: TGroupBox + inherited edtCounterName: TEdit + TabOrder = 41 + end + inherited edtCounterTrigger: TEdit + TabOrder = 39 + end inherited edtCounterCount: TEdit - TabOrder = 37 + TabOrder = 38 end inherited btnCounterAdd: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 40 end inherited btnCounterDelete: TButton Images = ImageList1 ImageIndex = 4 - TabOrder = 38 + TabOrder = 37 end inherited btnCounterP: TButton Images = ImageList1 ImageIndex = 0 Position.X = 416.000000000000000000 Size.Width = 22.000000000000000000 + TabOrder = 43 Text = '' end inherited btnCounterM: TButton @@ -684,13 +693,16 @@ object TTW_Bot: TTTW_Bot ImageIndex = 12 Position.X = 449.000000000000000000 Size.Width = 22.000000000000000000 + TabOrder = 44 Text = '' end inherited btnCounterEdit: TButton Images = ImageList1 ImageIndex = 3 + TabOrder = 45 end inherited sgCounter: TStringGrid + TabOrder = 46 Viewport.Width = 463.000000000000000000 Viewport.Height = 121.000000000000000000 inherited scCounterTrigger: TStringColumn diff --git a/uGeneral.pas b/uGeneral.pas index 1c30069..13257f3 100644 --- a/uGeneral.pas +++ b/uGeneral.pas @@ -61,11 +61,14 @@ type procedure frSettings1btnDAStartClick(Sender: TObject); procedure frCommands1btnRandAddClick(Sender: TObject); procedure frOBS1btnDeleteeChatClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); private { Private declarations } procedure ReadDB(); public { Public declarations } + procedure toLog(aModule, aMethod, aMessage: string; aCode: integer); + procedure GlobalExceptionHandler(Sender: TObject; E: Exception); end; var @@ -78,6 +81,15 @@ implementation {$R *.fmx} +procedure TTTW_Bot.GlobalExceptionHandler(Sender: TObject; E: Exception); +begin + try + TTW_Bot.toLog('GlobalException', E.ClassName, E.Message, 2); + except + // на случай, если логгер сам кинет исключение + end; +end; + procedure TTTW_Bot.cbThemeChange(Sender: TObject); begin cbTheme.ItemIndex := cbTheme.Items.IndexOf(cbTheme.text); @@ -89,7 +101,6 @@ end; procedure TTTW_Bot.FormCreate(Sender: TObject); var Path: string; - SearchRec: TSearchRec; function GetPathToTestExe: string; // вернет папку romaming begin @@ -149,7 +160,15 @@ begin for Path in TDirectory.GetFiles(myConst.stlPath) do cbTheme.Items.Add(ExtractFileName(Path)); cbTheme.ItemIndex := strtoint(db.ReadSetting('cbTheme', '-1')); + frLog1.FLogList := TList.Create; +end; +procedure TTTW_Bot.FormDestroy(Sender: TObject); +begin + FreeAndNil(db); + FreeAndNil(frAutoActions1.FTimerList); + FreeAndNil(frLog1.FLogList); + inherited; end; procedure TTTW_Bot.frCommands1btnRandAddClick(Sender: TObject); @@ -171,21 +190,13 @@ begin end; procedure TTTW_Bot.ReadDB; -var - I: Integer; - c: TComponent; - sl: TStringList; - - SavedColor: TAlphaColor; - - ColorStr: string; function XorDecryptToStrings(const InputFile, Key: string): TStrings; var InStream: TFileStream; MemStream: TMemoryStream; KeyBytes: TBytes; - KeyLen, KeyIndex: Integer; + KeyLen, KeyIndex: integer; B: Byte; begin // Преобразуем ключ в байты с использованием ANSI кодировки @@ -229,7 +240,7 @@ var // Загрузка компонентов настроек (TEdit, TCheckBox) procedure LoadSettingsComponents; var - I: Integer; + I: integer; c: TComponent; begin for I := 0 to frSettings1.ComponentCount - 1 do @@ -266,7 +277,7 @@ var procedure LoadEncryptedConfig; var sl: TStringList; - I: Integer; + I: integer; begin if not FileExists(myConst.cfg1) then Exit; @@ -318,7 +329,7 @@ var // Загрузка настроек уведомлений procedure LoadNotifySettings; var - I: Integer; + I: integer; c: TComponent; begin for I := 0 to frNotify1.ComponentCount - 1 do @@ -339,9 +350,9 @@ var // Загрузка настроек ИИ procedure LoadAISettings; var - I: Integer; + I: integer; c: TComponent; - ii: Integer; + ii: integer; // Настройки GigaChat procedure SetupGigaChatSettings; @@ -470,4 +481,35 @@ begin pwidechar('https://www.flaticon.com/ru/authors/karacis'), nil, nil, 1); end; +procedure TTTW_Bot.toLog(aModule, aMethod, aMessage: string; aCode: integer); +begin + TThread.Synchronize(nil, + procedure + var + ml: TRLog; + begin + // Инициализация всех полей записи + ml.rTime := Now; + case aCode of + 0: + ml.rType := 'INFO'; + 1: + ml.rType := 'WARNING'; + 2: + ml.rType := 'ERROR'; + 3: + ml.rType := 'DEBUG'; + else + ml.rType := 'UNKNOWN'; + end; + ml.rModule := aModule; // string + ml.rMethod := aMethod; // string + ml.rMessage := aMessage; // string + // Добавляем запись в список + frLog1.FLogList.Add(ml); + // Обновляем грид + frLog1.UpdateGridFilters; + end); +end; + end. diff --git a/uRecords.pas b/uRecords.pas index 6288713..d91562e 100644 --- a/uRecords.pas +++ b/uRecords.pas @@ -2,6 +2,15 @@ unit uRecords; interface +type + TRLog = record + rTime: ttime; + rType: string; + rModule: string; + rMethod: string; + rMessage: string; + end; + type TCounter = record counterName: string; diff --git a/uShowText.fmx b/uShowText.fmx index bdb0978..d9b9bde 100644 --- a/uShowText.fmx +++ b/uShowText.fmx @@ -1,7 +1,7 @@ object fShowText: TfShowText Left = 0 Top = 0 - Caption = 'fShowText' + Caption = #1057#1089#1099#1083#1082#1072' '#1085#1072' '#1072#1074#1090#1086#1088#1080#1079#1072#1094#1080#1102 ClientHeight = 295 ClientWidth = 498 FormFactor.Width = 320 @@ -16,8 +16,8 @@ object fShowText: TfShowText Size.Height = 262.000000000000000000 Size.PlatformDefault = False TabOrder = 0 - Viewport.Width = 494.000000000000000000 - Viewport.Height = 258.000000000000000000 + Viewport.Width = 498.000000000000000000 + Viewport.Height = 262.000000000000000000 end object Panel1: TPanel Align = Bottom diff --git a/uTTWAPI.pas b/uTTWAPI.pas index 2f033fe..65dcdae 100644 --- a/uTTWAPI.pas +++ b/uTTWAPI.pas @@ -7,6 +7,9 @@ uses IdMultipartFormData, DateUtils, uDataBase, System.Generics.Collections, uRecords; + type + TOnLog = procedure(aModul: string; aMethod: string; aMessage:string; aLevel:integer) of object; + type TTTW_API = class(TObject) private @@ -16,6 +19,7 @@ type channel_name_api: string; BotName_api: string; FChatBadges:tlist; + FOnLog:TOnLog; function GetFollowedAtFromJson(jsonString: string): string; function getTTW(method: string; ClientID: string; @@ -30,6 +34,8 @@ type function patchTTW(method: string; ClientID: string; params: TStringStream; isStreamer: boolean = false): string; overload; + + procedure toLog(alevel:integer; amethod:string; amessage:string); public constructor Create(Sender: TObject); destructor Destroy; override; @@ -66,6 +72,7 @@ type procedure GetChannelEmotes(var ce: Tlist); procedure GetGlobalEmotes(var ge: Tlist); function ValidateTwitchToken(const TokenName, TokenValue: string; var DayOfLive:integer): Boolean; + property OnLog: TOnLog read FOnLog write FOnLog; end; TChatBadges = TList; TEmotesList = TList; @@ -849,6 +856,12 @@ begin end; end; +procedure TTTW_API.toLog(alevel: integer; amethod, amessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uTTWAPI', aMethod, aMessage, aLevel); +end; + procedure TTTW_API.unBanUser(id: string); begin try @@ -862,7 +875,7 @@ begin except on E: Exception do //Form1.Log(2, 'TTTW_API.unBanUser', E.Message); - // flog.toLog(2,'TTW_API','unBanUser',E.Message); + toLog(2,'unBanUser',E.Message); end; end; @@ -876,7 +889,7 @@ begin except on E: Exception do //Form1.Log(2, 'TTTW_API.unRaid', E.Message); - // flog.toLog(2,'TTW_API','unRaid',E.Message); + toLog(2,'unRaid',E.Message); end; end; @@ -900,7 +913,7 @@ begin except on E: Exception do //Form1.Log(2, 'TTTW_API.UpdateCustomReward', E.Message); - // flog.toLog(2,'TTW_API','UpdateCustomReward',E.Message); + toLog(2,'UpdateCustomReward',E.Message); end; end; @@ -920,8 +933,8 @@ begin //Form1.Log(1, 'TTTW_API.UpdateRedemptionStatus', 'ChannelId: ' + qbid + // '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); - // flog.toLog(1,'TTW_API','UpdateRedemptionStatus','ChannelId: ' + qbid + - // '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); + toLog(1,'UpdateRedemptionStatus','ChannelId: ' + qbid + + '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); RequestData := TStringStream.Create('{"status":"CANCELED"}', CP_UTF8); try patchTTW('channel_points/custom_rewards/redemptions?broadcaster_id=' + @@ -933,7 +946,7 @@ begin except on E: Exception do // Form1.Log(2, 'TTTW_API.UpdateRedemptionStatus', E.Message); - // flog.toLog(2,'TTW_API','UpdateRedemptionStatus',E.Message); + toLog(2,'UpdateRedemptionStatus',E.Message); end; end; @@ -986,10 +999,10 @@ begin try if ResponseJSON.GetValue('expires_in') <> nil then begin - //fLog.toLog(0, 'TokenCheck', TokenName, - // Format('Токен действителен. Осталось: %d сек. Клиент: %s', - // [ResponseJSON.GetValue('expires_in').Value.ToInteger, - // ResponseJSON.GetValue('client_id').Value])); + toLog(0, 'ValidateTwitchToken', + Format('Токен действителен. Осталось: %d сек. Клиент: %s', + [ResponseJSON.GetValue('expires_in').Value.ToInteger, + ResponseJSON.GetValue('client_id').Value])); DayOfLive:=round(ResponseJSON.GetValue('expires_in').Value.ToInteger/60/60/24); end; Result := True; @@ -998,19 +1011,19 @@ begin end; except on E: Exception do - //fLog.toLog(2, 'TokenCheck', 'JSON Parse', E.Message); + toLog(2, 'ValidateTwitchToken', E.Message); end; end else if StatusCode = 401 then begin - //fLog.toLog(2, 'TokenCheck', TokenName, 'Invalid token'); + toLog(2, 'ValidateTwitchToken', 'Invalid token'); DayOfLive:=0; end else begin DayOfLive:=0; - // fLog.toLog(2, 'TokenCheck', TokenName, - // Format('HTTP %d: %s', [StatusCode, ResponseText])); + toLog(2, 'ValidateTwitchToken', + Format('HTTP %d: %s', [StatusCode, ResponseText])); end; finally @@ -1041,7 +1054,7 @@ begin except on E: Exception do //Form1.Log(2, 'TTTW_API.warnUser', E.Message); - // flog.toLog(2,'TTW_API','warnUser',E.Message); + toLog(2,'warnUser',E.Message); end; end; diff --git a/uTWAuth.pas b/uTWAuth.pas index 62b929b..9163a86 100644 --- a/uTWAuth.pas +++ b/uTWAuth.pas @@ -4,7 +4,7 @@ interface uses System.SysUtils, System.Classes, IdContext, IdCustomHTTPServer, IdHTTPServer, - IdComponent, ShellAPI; + IdComponent, ShellAPI, System.Threading, Windows; type TmyEvent = procedure(txt: string) of object; @@ -25,7 +25,6 @@ type AResponseInfo: TIdHTTPResponseInfo); procedure OnStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); - public constructor Create; destructor Destroy; override; @@ -33,44 +32,74 @@ type procedure StopServer; property OnToken: TmyEvent read FmyEvent write FmyEvent; property OnError: TmyEvent read FmyEvent write FmyEvent; - end; implementation +{ TTTWAuth } + constructor TTTWAuth.Create; begin + inherited Create; FHTTPServer := TIdHTTPServer.Create(nil); FHTTPServer.OnCommandGet := HandleRequest; FHTTPServer.OnStatus := OnStatus; + // Не включаем Active здесь end; destructor TTTWAuth.Destroy; begin - FHTTPServer.Free; + try + if Assigned(FHTTPServer) then + begin + try + if FHTTPServer.Active then + FHTTPServer.Active := False; + except + end; + FreeAndNil(FHTTPServer); + end; + except + end; inherited; end; procedure TTTWAuth.StartServer(aURL: string); begin + // Защита от повторного запуска + if Assigned(FHTTPServer) and FHTTPServer.Active then + Exit; + + // Очистим старые биндинги, чтобы не накапливать их + FHTTPServer.Bindings.Clear; + FHTTPServer.DefaultPort := 80; + // Добавляем биндинг явно FHTTPServer.Bindings.Add.SetBinding('127.0.0.1', 80); FURL := aURL; FHTTPServer.Active := True; if FURL <> '' then - ShellExecute(0, 'open', pwidechar(FURL), nil, nil, 1); + ShellExecute(0, 'open', PWideChar(FURL), nil, nil, SW_SHOWNORMAL); end; procedure TTTWAuth.StopServer; begin - FHTTPServer.Active := False; - + if Assigned(FHTTPServer) then + begin + try + FHTTPServer.Active := False; + except + end; + try + FHTTPServer.Bindings.Clear; + except + end; + end; end; procedure TTTWAuth.HandleRequest(ASender: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin - if ARequestInfo.Document = '/' then HandleRootRequest(ARequestInfo, AResponseInfo) else if ARequestInfo.Document = '/redirect' then @@ -87,85 +116,174 @@ end; procedure TTTWAuth.HandleRootRequest(ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin - AResponseInfo.ContentText := '' + #13 + '' + #13 + - '' + #13 + ' Redirecting...' + #13 + '' + #13 + - '' + #13 + '

получаю токен:

' + #13 + '' + #13 + '' + #13 + - ''; + AResponseInfo.ContentText := '' + sLineBreak + '' + + sLineBreak + '' + sLineBreak + + ' Redirecting...' + sLineBreak + '' + sLineBreak + + '' + sLineBreak + '

получаю токен:

' + sLineBreak + '' + sLineBreak + '' + sLineBreak + ''; end; procedure TTTWAuth.OnStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); begin - + // Можно логировать статус, но не обязательно end; procedure TTTWAuth.HandleDARequest(ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin - AResponseInfo.ContentText := '' + #13 + '' + #13 + - '' + #13 + ' Redirecting...' + #13 + '' + #13 + - '' + #13 + '

получаю код

' + #13 + '' + #13 + '' + #13 - + ''; + sLineBreak + 'urrl = urrl.replace(''#'',''?'');' + sLineBreak + + 'console.log(urrl);' + sLineBreak + 'window.location.href =urrl;' + + sLineBreak + ' ' + sLineBreak + '' + sLineBreak + ''; end; procedure TTTWAuth.HandleRedirectRequest(ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var - i: integer; + i: Integer; AccessToken: string; + LTokenCopy: string; begin + // Если получен access_token if Pos('access_token=', ARequestInfo.Params.Text) > 0 then begin for i := 0 to ARequestInfo.Params.Count - 1 do if Pos('access_token', ARequestInfo.Params[i]) > 0 then AccessToken := ARequestInfo.Params[i]; - AccessToken := StringReplace(AccessToken, 'access_token=', '', - [rfReplaceAll]); - AResponseInfo.ContentText := '' + #13 + '' + #13 + - '' + #13 + ' Done...' + #13 + '' + #13 + - '' + #13 + 'Эту страницу можно закрыть' + #13 + '' + #13 + - ''; + AccessToken := StringReplace(AccessToken, 'access_token=', '', [rfReplaceAll]); + + AResponseInfo.ContentText := '' + sLineBreak + '' + + sLineBreak + '' + sLineBreak + + ' Done...' + sLineBreak + '' + sLineBreak + + '' + sLineBreak + 'Эту страницу можно закрыть' + sLineBreak + + '' + sLineBreak + ''; AResponseInfo.WriteContent; - OnToken(AccessToken); - Destroy; + + // Копируем токен, чтобы корректно передать в main thread + LTokenCopy := AccessToken; + // Вызываем OnToken в main thread + if Assigned(FmyEvent) then + TThread.Queue(nil, + procedure + begin + try + FmyEvent(LTokenCopy); + except + end; + end); + + // Остановим сервер и запланируем очистку объекта в main thread + try + StopServer; + except + end; + TThread.Queue(nil, + procedure + begin + try + Free; + except + end; + end); + Exit; end; + + // Если есть error_description if Pos('error_description=', ARequestInfo.Params.Text) > 0 then begin for i := 0 to ARequestInfo.Params.Count - 1 do if Pos('error_description', ARequestInfo.Params[i]) > 0 then AccessToken := ARequestInfo.Params[i]; - AccessToken := StringReplace(AccessToken, 'error_description=', '', - [rfReplaceAll]); - AResponseInfo.ContentText := '' + #13 + '' + #13 + - '' + #13 + ' ERROR...' + #13 + '' + #13 + - '' + #13 + AccessToken + #13 + '' + #13 + - ''; + AccessToken := StringReplace(AccessToken, 'error_description=', '', [rfReplaceAll]); + + AResponseInfo.ContentText := '' + sLineBreak + '' + + sLineBreak + '' + sLineBreak + + ' ERROR...' + sLineBreak + '' + sLineBreak + + '' + sLineBreak + AccessToken + sLineBreak + '' + sLineBreak + ''; AResponseInfo.WriteContent; - OnError(AccessToken); - Destroy; + + LTokenCopy := AccessToken; + if Assigned(FmyEvent) then + TThread.Queue(nil, + procedure + begin + try + FmyEvent(LTokenCopy); + except + end; + end); + + try + StopServer; + except + end; + TThread.Queue(nil, + procedure + begin + try + Free; + except + end; + end); + Exit; end; + + // Если получен code= if Pos('code=', ARequestInfo.Params.Text) > 0 then begin for i := 0 to ARequestInfo.Params.Count - 1 do if Pos('code', ARequestInfo.Params[i]) > 0 then AccessToken := ARequestInfo.Params[i]; AccessToken := StringReplace(AccessToken, 'code=', '', [rfReplaceAll]); - AResponseInfo.ContentText := '' + #13 + '' + #13 + - '' + #13 + ' Done...' + #13 + '' + #13 + - '' + #13 + 'Эту страницу можно закрыть' + #13 + '' + #13 + - ''; + + AResponseInfo.ContentText := '' + sLineBreak + '' + + sLineBreak + '' + sLineBreak + + ' Done...' + sLineBreak + '' + sLineBreak + + '' + sLineBreak + 'Эту страницу можно закрыть' + sLineBreak + + '' + sLineBreak + ''; AResponseInfo.WriteContent; - OnToken(AccessToken); - Destroy; + + LTokenCopy := AccessToken; + if Assigned(FmyEvent) then + TThread.Queue(nil, + procedure + begin + try + FmyEvent(LTokenCopy); + except + end; + end); + + try + StopServer; + except + end; + TThread.Queue(nil, + procedure + begin + try + Free; + except + end; + end); + Exit; end; + + // По умолчанию — 404 + AResponseInfo.ResponseNo := 404; + AResponseInfo.ContentText := 'Not Found'; + AResponseInfo.WriteContent; end; end. + diff --git a/uWSDA.pas b/uWSDA.pas index f7b6c28..a187ec8 100644 --- a/uWSDA.pas +++ b/uWSDA.pas @@ -3,11 +3,12 @@ unit uWSDA; interface uses - Classes, SysUtils, System.JSON, ipwwsclient, StrUtils, uAPIDA; + Classes, SysUtils, System.JSON, ipwwsclient, StrUtils, uAPIDA; type TOnDonateEvent = procedure(aNick, aMessage, aSum: string) of object; - TOnStatusEvent = procedure(AStatusText: string; AStatusCode:integer) of object; + TOnStatusEvent = procedure(AStatusText: string; AStatusCode: integer) of object; + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; TWSClient = class(TObject) private @@ -15,16 +16,17 @@ type FAPIClient: TAPIClient; FOnDonate: TOnDonateEvent; FOnStatus: TOnStatusEvent; + FOnLog: TOnLog; FWsstoken: string; FWSID: string; - procedure DataIn(Sender: TObject; DataFormat: Integer; const Text: string; + procedure DataIn(Sender: TObject; DataFormat: integer; const Text: string; const TextB: TBytes; EOM, EOL: Boolean); procedure ConnectionStatus(Sender: TObject; const ConnectionEvent: string; - StatusCode: Integer; const Description: string); - procedure Error(Sender: TObject; ErrorCode: Integer; - const Description: string); + StatusCode: integer; const Description: string); + procedure Error(Sender: TObject; ErrorCode: integer; const Description: string); function ExtractValue(const T_, Text, _T: string): string; procedure HandleIncomingData(const Data: string); + procedure toLog(aLevel: integer; aMethod: string; aMessage: string); public constructor Create; destructor Destroy; override; @@ -33,6 +35,7 @@ type procedure Send(const Data: string); property OnDonate: TOnDonateEvent read FOnDonate write FOnDonate; property OnStatus: TOnStatusEvent read FOnStatus write FOnStatus; + property OnLog: TOnLog read FOnLog write FOnLog; property Wsstoken: string read FWsstoken write FWsstoken; property WSID: string read FWSID write FWSID; property APIClient: TAPIClient read FAPIClient write FAPIClient; @@ -42,60 +45,112 @@ implementation constructor TWSClient.Create; begin + inherited Create; FWS := TipwWSClient.Create(nil); FWS.OnDataIn := DataIn; FWS.OnConnectionStatus := ConnectionStatus; FWS.OnError := Error; - + FAPIClient := nil; + FOnDonate := nil; + FOnStatus := nil; + FOnLog := nil; end; destructor TWSClient.Destroy; begin - FWS.Disconnect; - FWS.Free; + try + if Assigned(FWS) then + begin + try + // очистим обработчики чтобы не было обратных вызовов в момент освобождения + FWS.OnDataIn := nil; + FWS.OnConnectionStatus := nil; + FWS.OnError := nil; + try + FWS.Disconnect; + except + // игнорируем ошибки при отключении + end; + finally + FreeAndNil(FWS); + end; + end; + except + // ничего не делаем — защита от исключений в деструкторе + end; inherited; end; procedure TWSClient.Disconnect; begin -FWS.Disconnect; + if Assigned(FWS) then + begin + try + FWS.Disconnect; + except + // игнорируем + end; + end; end; procedure TWSClient.Connect(const WSSURL: string); begin - FWS.ConnectTo(WSSURL); + if Assigned(FWS) then + begin + try + FWS.ConnectTo(WSSURL); + except + // логировать при необходимости + toLog(2, 'Connect', 'Exception on Connect'); + end; + end; end; procedure TWSClient.Send(const Data: string); begin - FWS.SendText(Data); + if Assigned(FWS) then + begin + try + FWS.SendText(Data); + except + toLog(2, 'Send', 'Exception on Send'); + end; + end; end; -procedure TWSClient.DataIn(Sender: TObject; DataFormat: Integer; +procedure TWSClient.toLog(aLevel: integer; aMethod: string; aMessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uWSDA', aMethod, aMessage, aLevel); +end; + +procedure TWSClient.DataIn(Sender: TObject; DataFormat: integer; const Text: string; const TextB: TBytes; EOM, EOL: Boolean); begin - HandleIncomingData(Text); - // FWS.Ping; + try + HandleIncomingData(Text); + except + on E: Exception do + toLog(2, 'DataIn', E.Message); + end; + //FWS.Ping; // если нужно end; -procedure TWSClient.ConnectionStatus(Sender: TObject; - const ConnectionEvent: string; StatusCode: Integer; - const Description: string); +procedure TWSClient.ConnectionStatus(Sender: TObject; const ConnectionEvent: string; + StatusCode: integer; const Description: string); begin if Assigned(FOnStatus) then - FOnStatus(ConnectionEvent,StatusCode); + FOnStatus(ConnectionEvent, StatusCode); end; -procedure TWSClient.Error(Sender: TObject; ErrorCode: Integer; - const Description: string); +procedure TWSClient.Error(Sender: TObject; ErrorCode: integer; const Description: string); begin -// fLog.toLog(2, 'uWSDA', 'Error', 'Code: ' + IntToStr(ErrorCode) + ' - ' + -// Description); + toLog(2, 'Error', '[' + IntToStr(ErrorCode) + '] ' + Description); end; function TWSClient.ExtractValue(const T_, Text, _T: string): string; var - StartPos, EndPos: Integer; + StartPos, EndPos: integer; begin StartPos := Pos(T_, Text); if StartPos = 0 then @@ -110,42 +165,59 @@ end; procedure TWSClient.HandleIncomingData(const Data: string); var JSON: TJSONObject; - jo: TJSONObject; DataObj: TJSONObject; - DonationData: TJSONObject; ChannelArray: TJSONArray; + jo: TJSONObject; wsstoken2: string; begin -// fLog.toLog(3, 'uWSDA', 'HandleIncomingData', Data); + toLog(3, 'HandleIncomingData', Data); // Обработка регистрации клиента if Pos('"result":{"client":"', Data) > 0 then begin FWsstoken := ExtractValue('"result":{"client":"', Data, '",'); - // fLog.toLog(3, 'uWSDA', 'HandleIncomingData', 'Клиент зарегистрирован'); - jo := FAPIClient.SubscribeToChannel(FWSID, FWsstoken); - // fLog.toLog(3, 'uWSDA', 'HandleIncomingData', 'Клиент подписан'); - ChannelArray := jo.Values['channels'] as TJSONArray; - if Assigned(ChannelArray) and (ChannelArray.Count > 0) then + toLog(3, 'HandleIncomingData', 'Клиент зарегистрирован'); + if Assigned(FAPIClient) then begin - wsstoken2 := ChannelArray.Items[0].GetValue('token'); - // fLog.toLog(3, 'da', 'EventWS', 'Подписка на канал с токеном: ' + - // wsstoken2); - FWS.SendText('{"params": {"channel": "$alerts:donation_' + FWSID + - '","token": "' + wsstoken2 + '"},"method": 1,"id": 2 }'); + try + jo := FAPIClient.SubscribeToChannel(FWSID, FWsstoken); + except + jo := nil; + end; + if Assigned(jo) then + try + toLog(3, 'HandleIncomingData', 'Клиент подписан'); + ChannelArray := jo.Values['channels'] as TJSONArray; + if Assigned(ChannelArray) and (ChannelArray.Count > 0) then + begin + wsstoken2 := ChannelArray.Items[0].GetValue('token'); + toLog(3, 'HandleIncomingData', 'Подписка на канал с токеном: ' + wsstoken2); + try + FWS.SendText('{"params": {"channel": "$alerts:donation_' + FWSID + '","token": "' + wsstoken2 + '"},"method": 1,"id": 2 }'); + except + toLog(2, 'HandleIncomingData', 'SendText failed'); + end; + end; + finally + jo.Free; + end; end; end; + // Обработка донатов if Pos('"name":"Donations"', Data) > 0 then begin - // fLog.toLog(3, 'uWSDA', 'HandleIncomingData', 'Новый Донат'); - JSON := TJSONObject.ParseJSONValue(Data) as TJSONObject; + toLog(3, 'HandleIncomingData', 'Новый Донат'); + JSON := nil; try - DataObj := JSON.GetValue('result').GetValue - ('data').GetValue('data'); - if Assigned(DataObj) and Assigned(FOnDonate) then - FOnDonate(DataObj.GetValue('username'), - DataObj.GetValue('message'), - DataObj.GetValue('amount')); + JSON := TJSONObject.ParseJSONValue(Data) as TJSONObject; + if Assigned(JSON) then + begin + DataObj := JSON.GetValue('result').GetValue('data').GetValue('data'); + if Assigned(DataObj) and Assigned(FOnDonate) then + FOnDonate(DataObj.GetValue('username'), + DataObj.GetValue('message'), + DataObj.GetValue('amount')); + end; finally JSON.Free; end; @@ -153,3 +225,4 @@ begin end; end. +