From 71cdcc9909063415b65d09bc3402ee8a69f1e667 Mon Sep 17 00:00:00 2001 From: "PC1\\PTyTb" Date: Fri, 8 Aug 2025 14:57:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=BD=20=D0=BE=D1=82=20=D0=BD=D0=B5=D0=B9=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D0=BA=D0=B8,=20=D0=B4=D0=BE=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D1=8F,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B3=D0=BB=D0=BE=D0=B1=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BB=D0=BE=D0=B3,=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B5=D0=BA=D0=BE=D0=BD=D0=B5=D0=BA=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BA=20=D0=94=D0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TTW_Bot_app.dpr | 43 +++++---- TTW_Bot_app.dproj | 46 +++++---- TTW_Bot_app_Icon.ico | Bin 0 -> 67646 bytes TTW_Bot_app_Icon1.ico | Bin 0 -> 93062 bytes fAutoActions.pas | 29 +++++- fFontSettings.pas | 3 +- fLog.fmx | 9 +- fLog.pas | 65 ++++++++++++- fOBS.pas | 4 +- fSettings.fmx | 24 ++--- fSettings.pas | 128 +++++++++++++++++++------ uAPIDA.pas | 24 +++-- uCreateChat.fmx | 13 ++- uCreateNotify.fmx | 27 ++++-- uCreateNotify.pas | 12 ++- uDataBase.pas | 16 +--- uGeneral.fmx | 22 ++++- uGeneral.pas | 72 +++++++++++--- uRecords.pas | 9 ++ uShowText.fmx | 6 +- uTTWAPI.pas | 43 ++++++--- uTWAuth.pas | 212 ++++++++++++++++++++++++++++++++---------- uWSDA.pas | 163 +++++++++++++++++++++++--------- 23 files changed, 721 insertions(+), 249 deletions(-) create mode 100644 TTW_Bot_app_Icon.ico create mode 100644 TTW_Bot_app_Icon1.ico 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 0000000000000000000000000000000000000000..da2865599c5a9985d591b7802bda960edcf863cf GIT binary patch literal 67646 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 literal 0 HcmV?d00001 diff --git a/TTW_Bot_app_Icon1.ico b/TTW_Bot_app_Icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..dca514ba007ec36a8948162f570e8baeebb2706b GIT binary patch literal 93062 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 O`F}QD{;U1ukNpd>Yhqvk literal 0 HcmV?d00001 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. +