From dacd2e60500063a35eb1f3637867a2529688be85 Mon Sep 17 00:00:00 2001 From: "PC1\\PTyTb" Date: Wed, 6 Aug 2025 14:54:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D1=8C=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9,=20=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D1=82=D1=8C=20=D0=91=D0=94?= =?UTF-8?q?=20=D0=BD=D0=B0=20records?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TTW_Bot_app.dpr | 14 +- TTW_Bot_app.dproj | 14 +- Win32/Debug/botapp.cfg | 5 + fAI.fmx | 6 + fAI.pas | 77 +++ fCommands.fmx | 10 +- fCommands.pas | 64 ++- fOBS.fmx | 10 + fOBS.pas | 1 + fSettings.fmx | 32 +- fSettings.pas | 256 ++++++++- uAPIDA.pas | 123 +++++ uDataBase.pas | 374 ++++++++----- uGeneral.fmx | 129 ++++- uGeneral.pas | 78 ++- uQ.fmx | 38 ++ uQ.pas | 46 ++ uRecords.pas | 7 + uShowText.fmx | 39 ++ uShowText.pas | 28 + uTTWAPI.pas | 1134 ++++++++++++++++++++++++++++++++++++++++ uTWAuth.pas | 171 ++++++ uWSDA.pas | 155 ++++++ 23 files changed, 2610 insertions(+), 201 deletions(-) create mode 100644 Win32/Debug/botapp.cfg create mode 100644 uAPIDA.pas create mode 100644 uQ.fmx create mode 100644 uQ.pas create mode 100644 uShowText.fmx create mode 100644 uShowText.pas create mode 100644 uTTWAPI.pas create mode 100644 uTWAuth.pas create mode 100644 uWSDA.pas diff --git a/TTW_Bot_app.dpr b/TTW_Bot_app.dpr index 5abd894..c615e52 100644 --- a/TTW_Bot_app.dpr +++ b/TTW_Bot_app.dpr @@ -3,7 +3,7 @@ program TTW_Bot_app; uses System.StartUpCopy, FMX.Forms, - uGeneral in 'uGeneral.pas' {Form1}, + uGeneral in 'uGeneral.pas' {TTW_Bot}, fSettings in 'fSettings.pas' {frSettings: TFrame}, fAI in 'fAI.pas' {frAI: TFrame}, fNotify in 'fNotify.pas' {frNotify: TFrame}, @@ -16,14 +16,22 @@ uses fColorSettings in 'fColorSettings.pas' {frColorSettings: TFrame}, uCreateChat in 'uCreateChat.pas' {fCreateChat}, fFontSettings in 'fFontSettings.pas' {frFontSettings: TFrame}, - uCreateNotify in 'uCreateNotify.pas' {fCreateNotify}; + uCreateNotify in 'uCreateNotify.pas' {fCreateNotify}, + uTWAuth in 'uTWAuth.pas', + uTTWAPI in 'uTTWAPI.pas', + uAPIDA in 'uAPIDA.pas', + uShowText in 'uShowText.pas' {fShowText}, + uWSDA in 'uWSDA.pas', + uQ in 'uQ.pas' {frmQ}; {$R *.res} begin Application.Initialize; - Application.CreateForm(TForm1, Form1); + Application.CreateForm(TTTW_Bot, TTW_Bot); 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 b0a77b9..da350aa 100644 --- a/TTW_Bot_app.dproj +++ b/TTW_Bot_app.dproj @@ -307,7 +307,7 @@ MainSource -
Form1
+
TTW_Bot
fmx
@@ -365,6 +365,18 @@
fCreateNotify
fmx
+ + + + +
fShowText
+ fmx +
+ + +
frmQ
+ fmx +
Base diff --git a/Win32/Debug/botapp.cfg b/Win32/Debug/botapp.cfg new file mode 100644 index 0000000..36df4ce --- /dev/null +++ b/Win32/Debug/botapp.cfg @@ -0,0 +1,5 @@ + QKPG@VrKK3q|#/<6# #?2r?3t&)_< +6s  q ,*< m.~|0256{|I *55w" KK8WZFD +-!2->1-7)WDB2,/7p-t+$kmSN? w t (!8+uv5( +4*,(3,+V)20B= +*u8+", (3&2ucR*"'>3&s ( s +W*!(B=&x{LR-r{s`SXKJ| q;kup%1K_FS_s%t<%%$ugRR \ No newline at end of file diff --git a/fAI.fmx b/fAI.fmx index 675708f..74def39 100644 --- a/fAI.fmx +++ b/fAI.fmx @@ -11,6 +11,8 @@ object frAI: TfrAI TabOrder = 43 Text = #1055#1086#1083#1091#1095#1080#1090#1100' GigaChat' TextSettings.Trimming = None + Visible = False + OnClick = btnGetAIDefClick end object edtAIP2: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] @@ -81,6 +83,7 @@ object frAI: TfrAI Size.PlatformDefault = False TabOrder = 40 Text = 'GigaChat' + OnChange = RBCustomChange end object RBCustom: TRadioButton Align = Top @@ -96,6 +99,7 @@ object frAI: TfrAI Size.PlatformDefault = False TabOrder = 39 Text = 'Custom' + OnChange = RBCustomChange end object rbDS: TRadioButton Align = Top @@ -111,6 +115,7 @@ object frAI: TfrAI Size.PlatformDefault = False TabOrder = 38 Text = 'DeepSeek' + OnChange = RBCustomChange end object rbCG: TRadioButton Align = Top @@ -126,6 +131,7 @@ object frAI: TfrAI Size.PlatformDefault = False TabOrder = 37 Text = 'ChatGPT' + OnChange = RBCustomChange end end object edtAIP3: TEdit diff --git a/fAI.pas b/fAI.pas index 6e08dee..5abeefb 100644 --- a/fAI.pas +++ b/fAI.pas @@ -29,8 +29,11 @@ type Label4: TLabel; edtKandiKey: TEdit; edtKandiSecret: TEdit; + procedure RBCustomChange(Sender: TObject); + procedure btnGetAIDefClick(Sender: TObject); private { Private declarations } + public { Public declarations } end; @@ -39,4 +42,78 @@ implementation {$R *.fmx} +uses uGeneral; + +procedure TfrAI.btnGetAIDefClick(Sender: TObject); +begin + rbGC.IsChecked := true; + if appconst.AI_GigaChat_ClientID <> '' then + edtAIP1.text := appconst.AI_GigaChat_ClientID; + if appconst.AI_GigaChat_AC <> '' then + edtAIP2.text := appconst.AI_GigaChat_AC; + + DB.WriteSetting('edtAIP1', edtAIP1.text); + DB.WriteSetting('edtAIP2', edtAIP2.text); +end; + +procedure TfrAI.RBCustomChange(Sender: TObject); +var + aiIndex: Integer; +begin + aiIndex := -1; + if TRadioButton(Sender).IsChecked then + aiIndex := strtoint(TRadioButton(Sender).Hint); + if aiIndex = -1 then + exit; + case aiIndex of + 0: + begin + Label45.text := 'ClientID'; + Label47.text := 'Autorization Code'; + Label1.Visible := false; + edtAIP2.Visible := true; + edtAIP2.Password := true; + edtAIP3.Visible := false; + cbOllama.IsChecked:=false; + cbOllama.Visible:=false; + end; + 1: + begin + Label45.text := 'API Token'; + Label47.text := ''; + Label1.Visible := false; + edtAIP2.Visible := false; + edtAIP2.Password := false; + edtAIP3.Visible := false; + cbOllama.IsChecked:=false; + cbOllama.Visible:=false; + end; + 2: + begin + Label45.text := 'API Token'; + Label47.text := ''; + Label1.Visible := false; + edtAIP2.Visible := false; + edtAIP2.Password := false; + edtAIP3.Visible := false; + cbOllama.IsChecked:=false; + cbOllama.Visible:=false; + end; + 3: + begin + Label45.text := 'API Token'; + Label47.text := 'URL'; + Label1.Visible := true; + edtAIP2.Visible := true; + edtAIP2.Password := false; + edtAIP3.Visible := true; + cbOllama.IsChecked:=false; + cbOllama.Visible:=true; + end; + end; + DB.WriteSetting('aiIndex', inttostr(aiIndex)); +end; + + + end. diff --git a/fCommands.fmx b/fCommands.fmx index 8a9de67..5220c92 100644 --- a/fCommands.fmx +++ b/fCommands.fmx @@ -10,7 +10,7 @@ object frCommands: TfrCommands Size.Width = 606.000000000000000000 Size.Height = 193.000000000000000000 Size.PlatformDefault = False - TabOrder = 6 + TabOrder = 5 RowCount = 0 Options = [ColumnResize, ColumnMove, ColLines, RowLines, Tabs, Header, HeaderClick, AutoDisplacement] Viewport.Width = 602.000000000000000000 @@ -306,7 +306,7 @@ object frCommands: TfrCommands Position.Y = 24.000000000000000000 TextSettings.Trimming = None Text = #1042#1072#1088#1080#1072#1085#1090#1099' '#1086#1090#1074#1077#1090#1072 - TabOrder = 42 + TabOrder = 43 end end object GroupBox8: TGroupBox @@ -360,9 +360,10 @@ object frCommands: TfrCommands object btnRandAdd: TButton Position.X = 9.000000000000000000 Position.Y = 205.000000000000000000 - TabOrder = 34 + TabOrder = 33 Text = #1044#1086#1073#1072#1074#1080#1090#1100 TextSettings.Trimming = None + OnClick = btnRandAddClick end object btnRandDel: TButton Position.X = 97.000000000000000000 @@ -370,9 +371,10 @@ object frCommands: TfrCommands Size.Width = 70.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 35 + TabOrder = 34 Text = #1059#1076#1072#1083#1080#1090#1100 TextSettings.Trimming = None + OnClick = btnRandDelClick end object sgRandomInt: TStringGrid CanFocus = True diff --git a/fCommands.pas b/fCommands.pas index 81e83ca..1e261d8 100644 --- a/fCommands.pas +++ b/fCommands.pas @@ -5,7 +5,7 @@ interface uses 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.Memo.Types, FMX.Grid, FMX.Layouts, + System.Rtti, FMX.Grid.Style, FMX.Memo.Types, FMX.Grid, FMX.Layouts, uRecords, FMX.ListBox, FMX.Memo, FMX.Edit, FMX.Controls.Presentation, FMX.ScrollBox; type @@ -82,14 +82,76 @@ type btnAIGenAdd: TButton; btnAIGenDel: TButton; btnAIGetTextUser: TButton; + procedure btnRandAddClick(Sender: TObject); + procedure btnRandDelClick(Sender: TObject); private { Private declarations } + procedure UpdateGridFromArray; public { Public declarations } + RandomCounters: TArray; end; implementation {$R *.fmx} +uses uGeneral; + +procedure TfrCommands.btnRandDelClick(Sender: TObject); +var + i, RowIndex: Integer; +begin + RowIndex := sgRandomInt.Row; + if (RowIndex < 0) or (RowIndex > High(RandomCounters)) then Exit; + + // Сдвигаем элементы массива + for i := RowIndex to High(RandomCounters)-1 do + RandomCounters[i] := RandomCounters[i+1]; + + SetLength(RandomCounters, Length(RandomCounters) - 1); + + UpdateGridFromArray; + DB.SaveGridToTable('sgRandomInt', sgRandomInt); +end; + +procedure TfrCommands.UpdateGridFromArray; +var + i: Integer; +begin + sgRandomInt.BeginUpdate; + try + sgRandomInt.RowCount := 1; // Сбрасываем строки (оставляем только заголовки) + + for i := 0 to High(RandomCounters) do + begin + sgRandomInt.RowCount := i + 1; + sgRandomInt.Cells[0, i] := RandomCounters[i].Name; + sgRandomInt.Cells[1, i] := IntToStr(RandomCounters[i].Ot); + sgRandomInt.Cells[2, i] := IntToStr(RandomCounters[i].ToValue); + end; + finally + sgRandomInt.EndUpdate; + end; +end; + +procedure TfrCommands.btnRandAddClick(Sender: TObject); +var + NewRec: TRandomCounters; +begin + NewRec.Name := edtRandomName.Text; + NewRec.Ot := StrToIntDef(edtOt.Text, 0); + NewRec.ToValue := StrToIntDef(edtTo.Text, 100); + + SetLength(RandomCounters, Length(RandomCounters) + 1); + RandomCounters[High(RandomCounters)] := NewRec; + + UpdateGridFromArray; + // DB.SaveGridToTable('sgRandomInt', sgRandomInt); + DB.SaveRecordArray('RandomCounters', RandomCounters); + edtRandomName.Text := ''; + edtOt.Text := '0'; + edtTo.Text := '100'; +end; + end. diff --git a/fOBS.fmx b/fOBS.fmx index 5679148..15a0152 100644 --- a/fOBS.fmx +++ b/fOBS.fmx @@ -60,4 +60,14 @@ object frOBS: TfrOBS Text = #1057#1086#1079#1076#1072#1090#1100' '#1086#1087#1086#1074#1077#1097#1077#1085#1080#1077 TextSettings.Trimming = None end + object btnCreateOBSKandinsky: TButton + Position.X = 257.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 147.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 5 + Text = #1057#1086#1079#1076#1072#1090#1100' '#1050#1072#1085#1076#1080#1085#1089#1082#1080#1081 + TextSettings.Trimming = None + end end diff --git a/fOBS.pas b/fOBS.pas index 7c33c37..0386960 100644 --- a/fOBS.pas +++ b/fOBS.pas @@ -18,6 +18,7 @@ type StringColumn1: TStringColumn; StringColumn2: TStringColumn; btnCreateOBSNotify: TButton; + btnCreateOBSKandinsky: TButton; private { Private declarations } public diff --git a/fSettings.fmx b/fSettings.fmx index 9bb6c90..9584a3c 100644 --- a/fSettings.fmx +++ b/fSettings.fmx @@ -98,6 +98,7 @@ object frSettings: TfrSettings TabOrder = 3 Text = #1055#1086#1083#1091#1095#1080#1090#1100' Token' TextSettings.Trimming = None + OnClick = btnGetTokenClick Left = 207 Top = 87 end @@ -133,9 +134,10 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 35 + TabOrder = 34 Text = #1054#1090#1082#1088#1099#1090#1100' '#1089#1090#1088#1080#1084 TextSettings.Trimming = None + OnClick = btnOpenStreamClick end object btnGetTokenStreamer: TButton Position.X = 193.000000000000000000 @@ -143,13 +145,14 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 36 + TabOrder = 35 Text = #1055#1086#1083#1091#1095#1080#1090#1100' Token' TextSettings.Trimming = None + OnClick = btnGetTokenStreamerClick end object edtBotTokenStreamer: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 37 + TabOrder = 36 Password = True Position.X = 8.000000000000000000 Position.Y = 146.000000000000000000 @@ -191,13 +194,14 @@ object frSettings: TfrSettings TabOrder = 45 Text = #1055#1086#1083#1091#1095#1080#1090#1100 TextSettings.Trimming = None + OnClick = btnDAGetCodeClick end object Label63: TLabel Position.X = 8.000000000000000000 Position.Y = 24.000000000000000000 TextSettings.Trimming = None Text = 'Client ID' - TabOrder = 37 + TabOrder = 36 end object edtDAClientID: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] @@ -269,8 +273,10 @@ object frSettings: TfrSettings TabOrder = 46 Text = #1055#1086#1076#1082#1083#1102#1095#1080#1090#1100#1089#1103 TextSettings.Trimming = None + OnClick = btnDAStartClick end object btnGetDADef: TButton + ImageIndex = 10 Position.X = 8.000000000000000000 Position.Y = 274.000000000000000000 Size.Width = 209.000000000000000000 @@ -282,8 +288,8 @@ object frSettings: TfrSettings Visible = False end object cbDAAutoLogin: TCheckBox - Position.X = 8.000000000000000000 - Position.Y = 274.000000000000000000 + Position.X = 137.000000000000000000 + Position.Y = 246.000000000000000000 Size.Width = 88.000000000000000000 Size.Height = 19.000000000000000000 Size.PlatformDefault = False @@ -300,6 +306,7 @@ object frSettings: TfrSettings TabOrder = 2 Text = #1054#1090#1082#1088#1099#1090#1100' '#1087#1072#1087#1082#1091' '#1089' '#1085#1072#1089#1090#1088#1086#1081#1082#1072#1084#1080 TextSettings.Trimming = None + OnClick = btnOpenRomaningClick end object btnImportSettings: TButton Position.X = 201.000000000000000000 @@ -310,6 +317,7 @@ object frSettings: TfrSettings TabOrder = 3 Text = #1047#1072#1075#1088#1091#1079#1080#1090#1100' '#1089#1074#1086#1080' '#1085#1072#1089#1090#1088#1086#1081#1082#1080 TextSettings.Trimming = None + OnClick = btnImportSettingsClick end object btnExportSettings: TButton Position.X = 201.000000000000000000 @@ -320,9 +328,10 @@ object frSettings: TfrSettings TabOrder = 4 Text = #1042#1099#1075#1088#1091#1079#1080#1090#1100' '#1085#1072#1089#1090#1088#1086#1081#1082#1080 TextSettings.Trimming = None + OnClick = btnExportSettingsClick end object btnMaster: TButton - Position.X = 440.000000000000000000 + Position.X = 369.000000000000000000 Position.Y = 321.000000000000000000 Size.Width = 193.000000000000000000 Size.Height = 22.000000000000000000 @@ -330,5 +339,14 @@ object frSettings: TfrSettings TabOrder = 7 Text = #1047#1072#1087#1091#1089#1090#1080#1090#1100' '#1084#1072#1089#1090#1077#1088' '#1085#1072#1089#1090#1088#1086#1081#1082#1080 TextSettings.Trimming = None + OnClick = btnMasterClick + end + object SaveDialog1: TSaveDialog + Left = 513 + Top = 272 + end + object OpenDialog1: TOpenDialog + Left = 312 + Top = 184 end end diff --git a/fSettings.pas b/fSettings.pas index 2576029..87f3d34 100644 --- a/fSettings.pas +++ b/fSettings.pas @@ -3,9 +3,12 @@ unit fSettings; interface uses - System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, uQ, FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, - FMX.Controls.Presentation, FMX.Edit; + ShellAPI, system.IOUtils, uDataBase, + FMX.Controls.Presentation, FMX.Edit, uTWAuth, uRecords, uAPIDA, uShowText, + json, uWSDA; type TfrSettings = class(TFrame) @@ -42,14 +45,263 @@ type btnImportSettings: TButton; btnExportSettings: TButton; btnMaster: TButton; + SaveDialog1: TSaveDialog; + OpenDialog1: TOpenDialog; + procedure btnGetTokenClick(Sender: TObject); + procedure btnGetTokenStreamerClick(Sender: TObject); + procedure btnOpenStreamClick(Sender: TObject); + procedure btnDAGetCodeClick(Sender: TObject); + procedure btnDAStartClick(Sender: TObject); + procedure btnOpenRomaningClick(Sender: TObject); + procedure btnImportSettingsClick(Sender: TObject); + procedure btnExportSettingsClick(Sender: TObject); + procedure btnMasterClick(Sender: TObject); private { Private declarations } + FAPIClient: TAPIClient; + forbot: boolean; + + procedure OnTTWToken(txt: string); + procedure OnTokenDA(txt: string); + procedure HandleWSStatus(AStatusText: string; AStatusCode: integer); + procedure HandleWSDonate(aNick, aMessage, aSum: string); public { Public declarations } + FWSClient: TWSClient; + procedure init(); end; implementation {$R *.fmx} +uses uGeneral; + +procedure TfrSettings.btnDAGetCodeClick(Sender: TObject); +var + twa: TTTWAuth; + Url: string; +begin + if (edtDAClientSecret.text = '') or (edtDAClientID.text = '') or + (edtDARedirectURL.text = '') then + exit; + + Url := 'https://www.donationalerts.com/oauth/authorize?client_id=' + + edtDAClientID.text + + '&redirect_uri=http://localhost/da&response_type=code&scope=oauth-user-show+oauth-donation-subscribe'; + twa := TTTWAuth.Create; + twa.OnToken := OnTokenDA; + twa.StartServer(Url); +end; + +procedure TfrSettings.OnTokenDA(txt: string); +begin + edtDACode.text := txt; + if cbDAAutoLogin.IsChecked then + begin + btnDAStartClick(self); + end; +end; + +procedure TfrSettings.btnDAStartClick(Sender: TObject); +var + UserInfo: TJSONObject; + Data: TJSONObject; +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'); + 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 := 'Подключиться'; + end; +end; + +procedure TfrSettings.btnExportSettingsClick(Sender: TObject); +var + DestinationFile: string; +begin + SaveDialog1.FileName := TPath.GetFileName(myConst.DBPath); + if SaveDialog1.Execute then + begin + DestinationFile := SaveDialog1.FileName; + TFile.Copy(myConst.DBPath, DestinationFile, True); + end; +end; + +procedure TfrSettings.btnGetTokenClick(Sender: TObject); +var + s: string; + sope: string; + ttw_Auth: TTTWAuth; +begin + ttw_Auth := TTTWAuth.Create; + ttw_Auth.OnToken := OnTTWToken; + sope := 'moderator:manage:shoutouts' + '+moderator:manage:announcements' + + '+moderator:manage:banned_users' + '+moderator:manage:warnings' + + '+moderator:read:followers' + '+channel:manage:raids' + + '+channel:manage:moderators' + '+channel:read:redemptions' + '+chat:read' + + '+chat:edit+user:read:emotes'; + sope := StringReplace(sope, ':', '%3A', [rfReplaceAll]); + + s := 'https://id.twitch.tv/oauth2/authorize?client_id=' + edtBotClientID.text + + '&redirect_uri=http://localhost&response_type=token&' + 'scope=' + sope; + ttw_Auth.StartServer(''); + forbot := True; + fShowText.Memo1.Lines.text := s; + fShowText.Show; + fShowText.Memo1.WordWrap := True; + +end; + +procedure TfrSettings.btnGetTokenStreamerClick(Sender: TObject); +var + sope: string; + ttw_Auth: TTTWAuth; +begin + ttw_Auth := TTTWAuth.Create; + ttw_Auth.OnToken := OnTTWToken; + sope := 'channel:read:redemptions' + '+channel:manage:vips' + + '+moderator:read:followers' + '+channel:read:subscriptions' + + '+channel:manage:moderators' + '+channel:manage:redemptions'; + sope := StringReplace(sope, ':', '%3A', [rfReplaceAll]); + ttw_Auth.StartServer('https://id.twitch.tv/oauth2/authorize?client_id=' + + edtBotClientID.text + '&redirect_uri=http://localhost&response_type=token&' + + 'scope=' + sope); + forbot := false; +end; + +procedure TfrSettings.btnImportSettingsClick(Sender: TObject); +var + SourceFile, DestinationDir, DestinationFile: string; +begin + if OpenDialog1.Execute then + begin + DB.Free; + SourceFile := OpenDialog1.FileName; + DestinationDir := myConst.DBPath; + DestinationFile := myConst.DBPath; + TFile.Copy(SourceFile, DestinationFile, True); + DB := TSettingsDatabase.Create(myConst.DBPath); + end; +end; + +procedure TfrSettings.btnMasterClick(Sender: TObject); +var + qf: tfrmq; + +begin + qf := tfrmq.Create(nil); + try + qf.SetLabelText('Введите ник аккаунта от которого будет писать бот:'); + if qf.ShowModal = mrOk then + edtBotName.text := qf.GetEditText; + qf.SetLabelText('Введите ник стримера где будет работать бот:'); + if qf.ShowModal = mrOk then + edtChannel.text := qf.GetEditText; + if btnGetClientID.Visible then + begin + edtBotClientID.text := appconst.TTV_ClientID; + showmessage('Появится окно, там будет ссылка. ' + #13 + + 'Скопируй ее и открой в браузере где авторизован бот. ' + #13 + + 'Если понял - жми ОК'); + btnGetTokenClick(self); + showmessage + ('Когда ссылка пропадет жми "Получить Token" около поля "API Token Стримера". ' + + #13 + 'Если понял - жми ОК'); + end + else + begin + showmessage('Нет файла ключей! ' + #13 + + 'Введите ClientID вручную и продолжите настройку без мастера!'); + end; + + finally + qf.Free; + end; +end; + +procedure TfrSettings.btnOpenRomaningClick(Sender: TObject); +begin + ShellExecute(0, 'open', pwidechar(ExtractFilePath(myConst.DBPath)), + nil, nil, 1); +end; + +procedure TfrSettings.btnOpenStreamClick(Sender: TObject); +begin + ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/' + edtChannel.text), + nil, nil, 1); +end; + +procedure TfrSettings.init; +begin + FAPIClient := TAPIClient.Create; + FWSClient := TWSClient.Create; + FWSClient.OnStatus := HandleWSStatus; + FWSClient.OnDonate := HandleWSDonate; +end; + +procedure TfrSettings.OnTTWToken(txt: string); +begin + fShowText.Close; + if forbot then + begin + edtBotToken.text := txt; + DB.WriteSetting('edtBotToken', txt); + end + else + begin + edtBotTokenStreamer.text := txt; + DB.WriteSetting('edtBotTokenStreamer', txt); + end; + +end; + +procedure TfrSettings.HandleWSDonate(aNick, aMessage, aSum: string); +begin + // fDonats.OnDADonate(aNick, aMessage, aSum); +end; + +procedure TfrSettings.HandleWSStatus(AStatusText: string; AStatusCode: integer); +begin + // fLog.tolog(3,'uLogin','HandleWSStatus',AStatusText); + TTW_Bot.Label8.text := AStatusText; + case AStatusCode of + 0: + begin + btnDAStart.ImageIndex := 1; + btnDAStart.text := 'Отключиться'; + end; + else + begin + btnDAStart.ImageIndex := 18; + btnDAStart.text := 'Подключиться'; + end; + end; + +end; + end. diff --git a/uAPIDA.pas b/uAPIDA.pas new file mode 100644 index 0000000..0b401bd --- /dev/null +++ b/uAPIDA.pas @@ -0,0 +1,123 @@ +unit uAPIDA; + +interface + +uses + Classes, SysUtils, System.JSON, IdHTTP, IdSSLOpenSSL, flog; + +type + TAPIClient = class(TObject) + private + FHttpClient: TIdHTTP; + FSSLHandler: TIdSSLIOHandlerSocketOpenSSL; + FToken: string; + procedure SetToken(const Value: string); + procedure CheckHTTPError(AResponseCode: Integer; const AResponse: string); + public + constructor Create; + destructor Destroy; override; + function GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string; + function GetUserInfo: TJSONObject; + function SubscribeToChannel(const uid, clientUID: string): TJSONObject; + property Token: string read FToken write SetToken; + end; + +implementation + + + +const + ContentType = 'application/x-www-form-urlencoded'; + UserAgent = 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + AuthorizationHeader = 'Authorization: Bearer '; + +constructor TAPIClient.Create; +begin + inherited; + FHttpClient := TIdHTTP.Create(nil); + FSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(FHttpClient); + FSSLHandler.SSLOptions.Method := sslvSSLv23; + FHttpClient.IOHandler := FSSLHandler; + FHttpClient.Request.UserAgent := UserAgent; + FHttpClient.Request.ContentType := ContentType; + FHttpClient.HandleRedirects := True; +end; + +destructor TAPIClient.Destroy; +begin + FHttpClient.Free; + inherited; +end; + +procedure TAPIClient.CheckHTTPError(AResponseCode: Integer; const AResponse: string); +begin + if AResponseCode <> 200 then + raise Exception.CreateFmt('HTTP Error %d: %s', [AResponseCode, AResponse]); +end; + +function TAPIClient.GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string; +var + Response: string; + Stream: TStringStream; + Json: TJSONObject; +begin + Stream := TStringStream.Create( + Format('grant_type=authorization_code&client_id=%s&client_secret=%s&redirect_uri=%s&code=%s', + [client_id, client_secret, redirect_uri, code]), TEncoding.UTF8); + try + Response := FHttpClient.Post('https://www.donationalerts.com/oauth/token', Stream); + CheckHTTPError(FHttpClient.ResponseCode, Response); + + Json := TJSONObject.ParseJSONValue(Response) as TJSONObject; + try + Result := Json.GetValue('access_token'); + FToken := Result; + finally + Json.Free; + end; + finally + Stream.Free; + end; +end; + +function TAPIClient.GetUserInfo: TJSONObject; +var + Response: string; +begin + FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken); + try + Response := FHttpClient.Get('https://www.donationalerts.com/api/v1/user/oauth'); + CheckHTTPError(FHttpClient.ResponseCode, Response); + Result := TJSONObject.ParseJSONValue(Response) as TJSONObject; + finally + FHttpClient.Request.CustomHeaders.Clear; + end; +end; + +function TAPIClient.SubscribeToChannel(const uid, clientUID: string): TJSONObject; +var + Response: string; + Stream: TStringStream; + RequestJSON: string; +begin + RequestJSON := Format('{"channels":["$alerts:donation_%s"], "client":"%s"}', [uid, clientUID]); + Stream := TStringStream.Create(RequestJSON, TEncoding.UTF8); + try + FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken); + FHttpClient.Request.ContentType := 'application/json'; + Response := FHttpClient.Post('https://www.donationalerts.com/api/v1/centrifuge/subscribe', Stream); + CheckHTTPError(FHttpClient.ResponseCode, Response); + Result := TJSONObject.ParseJSONValue(Response) as TJSONObject; + // .toLog(3,'uAPIDA','SubscribeToChannel',Result.ToJSON); + finally + Stream.Free; + FHttpClient.Request.CustomHeaders.Clear; + end; +end; + +procedure TAPIClient.SetToken(const Value: string); +begin + FToken := Value; +end; + +end. diff --git a/uDataBase.pas b/uDataBase.pas index 334c781..8ba4da3 100644 --- a/uDataBase.pas +++ b/uDataBase.pas @@ -7,13 +7,12 @@ uses FMX.Grid, FireDAC.Stan.Def, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef, - FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLiteWrapper.Stat, FireDAC.VCLUI.Wait, + FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLiteWrapper.Stat, FireDAC.FMXUI.Wait, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, Data.DB, FireDAC.Comp.DataSet, - FireDAC.FMXUI.Wait, FireDAC.Comp.UI, uRecords, System.Generics.Collections; + FireDAC.Comp.UI, uRecords, System.Generics.Collections, System.Rtti, System.TypInfo; type TSettingsDatabase = class - private FConnection: TFDConnection; procedure InitializeDatabase; @@ -23,32 +22,27 @@ type function GetColumnsList(Grid: TStringGrid): string; function GetValuesPlaceholders(Grid: TStringGrid): string; function CheckTableExists(const TableName: string): Boolean; + procedure EnsureTableForRecord(const TableName: string; RecordTypeInfo: PTypeInfo); +// function GetFieldDefinition(Field: TRttiField): string; + function GetSQLType(Field: TRttiField): string; + function TableHasColumn(const TableName, ColumnName: string): Boolean; public FChannel: string; - constructor Create(const DatabasePath: string); destructor Destroy; override; - - // Методы для работы с настройками function ReadSetting(const Name: string; Default: string = ''): string; procedure WriteSetting(const Name, Value: string); - function getLoginData(): TLogin; - - // Методы для работы с TStringGrid + function getLoginData: TLogin; procedure SaveGridToTable(const TableName: string; Grid: TStringGrid); procedure LoadGridFromTable(const TableName: string; Grid: TStringGrid); - - // Методы для работы с Users - // procedure SaveUsers(const Users: array of User); - procedure LoadUsers(var users: tlist); - - // Методы для работы с Групповыми ответами + procedure LoadUsers(var users: TList); procedure addGroupResponse(Name, Respons: string); - procedure getGroupResponse(aName: string; const lbResponse: Tstrings); - procedure getGroupName(const lbName: Tstrings); + procedure getGroupResponse(aName: string; const lbResponse: TStrings); + procedure getGroupName(const lbName: TStrings); procedure delGroupName(aName: string); procedure delGroupResponse(aName, aResponse: string); - + procedure SaveRecordArray(const TableName: string; const Items: array of T); + procedure LoadRecordArray(const TableName: string; var Items: TArray); end; implementation @@ -67,8 +61,7 @@ begin on E: Exception do begin FreeAndNil(FConnection); - raise Exception.Create('Ошибка при подключении к базе данных: ' + - E.Message); + raise Exception.Create('Ошибка при подключении к базе данных: ' + E.Message); end; end; end; @@ -80,7 +73,8 @@ begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - Query.SQL.Text := 'delete FROM GroupResponse where Name = "' + aName + '"'; + Query.SQL.Text := 'DELETE FROM GroupResponse WHERE Name = :name'; + Query.ParamByName('name').AsString := aName; Query.ExecSQL; finally Query.Free; @@ -94,10 +88,9 @@ begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - Query.SQL.Text := 'delete FROM GroupResponse where Name = :name' + - ' and Response = :response'; - Query.Params.ParamByName('name').AsString := aName; - Query.Params.ParamByName('response').AsString := aResponse; + Query.SQL.Text := 'DELETE FROM GroupResponse WHERE Name = :name AND Response = :response'; + Query.ParamByName('name').AsString := aName; + Query.ParamByName('response').AsString := aResponse; Query.ExecSQL; finally Query.Free; @@ -117,16 +110,14 @@ var FDQuery: TFDQuery; FieldExists: Boolean; begin - // Создаем таблицу, если она не существует FConnection.ExecSQL('CREATE TABLE IF NOT EXISTS params (' + ' name TEXT PRIMARY KEY,' + ' value TEXT' + ');'); - // Создаем таблицу для пользователей, если она не существует + FConnection.ExecSQL('CREATE TABLE IF NOT EXISTS users (' + ' id TEXT PRIMARY KEY,' + ' login TEXT,' + ' DisplayName TEXT,' + ' created_at DATETIME,' + ' follow_at DATETIME,' + ' isVip TEXT,' + - ' isModer TEXT,' + ' isO TEXT,' + ' streamer TEXT' + + ' isModer TEXT,' + ' isO TEXT,' + ' streamer TEXT' + ');'); - ');'); FDQuery := TFDQuery.Create(nil); try FDQuery.Connection := FConnection; @@ -144,71 +135,19 @@ begin end; FDQuery.Close; if not FieldExists then - begin FConnection.ExecSQL('ALTER TABLE users ADD COLUMN streamer TEXT'); - end; finally FDQuery.Free; end; - FConnection.ExecSQL - ('CREATE TABLE IF NOT EXISTS GroupResponse (ID INTEGER PRIMARY KEY, Name TEXT, Response TEXT);'); - + FConnection.ExecSQL('CREATE TABLE IF NOT EXISTS GroupResponse (ID INTEGER PRIMARY KEY, Name TEXT, Response TEXT);'); end; -{ procedure TSettingsDatabase.SaveUsers(const Users: array of User); - var - Query: TFDQuery; - UserItem: User; - s: string; - begin - if Length(Users) < 1 then - exit; - // Вставляем данные из массива пользователей в таблицу - Query := TFDQuery.Create(nil); - try - Query.Connection := FConnection; - for UserItem in Users do - begin - Query.SQL.Text := - 'INSERT OR REPLACE INTO users (id, login, DisplayName, created_at, follow_at, isVip, isModer, isO, streamer) ' - + 'VALUES (:id, :login, :DisplayName, :created_at, :follow_at, :isVip, :isModer, :isO, :streamer);'; - Query.ParamByName('id').AsString := UserItem.id; - Query.ParamByName('login').AsString := UserItem.login; - Query.ParamByName('DisplayName').AsString := UserItem.DisplayName; - Query.ParamByName('created_at').AsDateTime := UserItem.created_at; - Query.ParamByName('follow_at').AsDateTime := UserItem.follow_at; - Query.ParamByName('streamer').AsString := FChannel; - - if UserItem.isVip then - s := 'True' - else - s := 'False'; - Query.ParamByName('isVip').AsString := s; - if UserItem.isModer then - s := 'True' - else - s := 'False'; - Query.ParamByName('isModer').AsString := s; - if UserItem.isO then - s := 'True' - else - s := 'False'; - Query.ParamByName('isO').AsString := s; - Query.ExecSQL; - end; - finally - Query.Free; - end; - end; } - -procedure TSettingsDatabase.LoadUsers(var users: tlist); +procedure TSettingsDatabase.LoadUsers(var users: TList); var Query: TFDQuery; - UserItem: tuser; - + UserItem: TUser; begin - Query := TFDQuery.Create(nil); try Query.Connection := FConnection; @@ -230,7 +169,6 @@ begin users.Add(UserItem); Query.Next; end; - finally Query.Free; end; @@ -262,14 +200,13 @@ begin end; end; -procedure TSettingsDatabase.getGroupName(const lbName: Tstrings); +procedure TSettingsDatabase.getGroupName(const lbName: TStrings); var Query: TFDQuery; begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - lbName.Clear; Query.SQL.Text := 'SELECT DISTINCT Name FROM GroupResponse'; Query.Open; @@ -283,18 +220,16 @@ begin end; end; -procedure TSettingsDatabase.getGroupResponse(aName: string; - const lbResponse: Tstrings); +procedure TSettingsDatabase.getGroupResponse(aName: string; const lbResponse: TStrings); var Query: TFDQuery; begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - lbResponse.Clear; - Query.SQL.Text := 'SELECT * FROM GroupResponse WHERE name=:TableName'; - Query.ParamByName('TableName').AsString := aName; + Query.SQL.Text := 'SELECT Response FROM GroupResponse WHERE Name = :name'; + Query.ParamByName('name').AsString := aName; Query.Open; while not Query.EOF do begin @@ -304,7 +239,6 @@ begin finally Query.Free; end; - end; function TSettingsDatabase.getLoginData: TLogin; @@ -346,9 +280,9 @@ begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - Query.SQL.Text := - Format('INSERT INTO GroupResponse (Name, Response) VALUES (''%s'', ''%s'')', - [Name, Respons]); + Query.SQL.Text := 'INSERT INTO GroupResponse (Name, Response) VALUES (:name, :response)'; + Query.ParamByName('name').AsString := Name; + Query.ParamByName('response').AsString := Respons; Query.ExecSQL; finally Query.Free; @@ -363,8 +297,7 @@ begin Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - Query.SQL.Text := - 'SELECT COUNT(*) FROM sqlite_master WHERE type=''table'' AND name=:TableName'; + Query.SQL.Text := 'SELECT COUNT(*) FROM sqlite_master WHERE type=''table'' AND name=:TableName'; Query.ParamByName('TableName').AsString := TableName; Query.Open; Result := (Query.Fields[0].AsInteger > 0); @@ -373,40 +306,27 @@ begin end; end; -procedure TSettingsDatabase.LoadGridFromTable(const TableName: string; - Grid: TStringGrid); +procedure TSettingsDatabase.LoadGridFromTable(const TableName: string; Grid: TStringGrid); var Query: TFDQuery; Col, Row: Integer; begin + if not CheckTableExists(TableName) then Exit; + Query := TFDQuery.Create(nil); try Query.Connection := FConnection; - - // Проверить наличие таблицы - if not CheckTableExists(TableName) then - begin - // Таблица не существует, выходим из процедуры - exit; - end; - Query.SQL.Text := 'SELECT * FROM ' + TableName; Query.Open; - // Очищаем Grid Grid.RowCount := 0; - - // Заполняем Grid данными из таблицы while not Query.EOF do begin Row := Grid.RowCount; Grid.RowCount := Grid.RowCount + 1; for Col := 0 to Grid.ColumnCount - 1 do - begin - Grid.Cells[Col, Row] := Query.FieldByName('col' + IntToStr(Col) - ).AsString; - end; + Grid.Cells[Col, Row] := Query.FieldByName('col' + IntToStr(Col)).AsString; Query.Next; end; @@ -415,33 +335,22 @@ begin end; end; -procedure TSettingsDatabase.SaveGridToTable(const TableName: string; - Grid: TStringGrid); +procedure TSettingsDatabase.SaveGridToTable(const TableName: string; Grid: TStringGrid); var Query: TFDQuery; Col, Row: Integer; begin - // Удаляем старую таблицу, если она существует FConnection.ExecSQL('DROP TABLE IF EXISTS ' + TableName); + FConnection.ExecSQL('CREATE TABLE ' + TableName + ' (id INTEGER PRIMARY KEY AUTOINCREMENT, ' + GetColumnsDefinition(Grid) + ');'); - // Создаем новую таблицу - FConnection.ExecSQL('CREATE TABLE ' + TableName + ' (' + - ' id INTEGER PRIMARY KEY AUTOINCREMENT,' + ' ' + - GetColumnsDefinition(Grid) + ');'); - - // Вставляем данные из Grid в таблицу Query := TFDQuery.Create(nil); try Query.Connection := FConnection; for Row := 0 to Grid.RowCount - 1 do begin - Query.SQL.Text := 'INSERT INTO ' + TableName + ' (' + GetColumnsList(Grid) - + ') VALUES (' + GetValuesPlaceholders(Grid) + ')'; + Query.SQL.Text := 'INSERT INTO ' + TableName + ' (' + GetColumnsList(Grid) + ') VALUES (' + GetValuesPlaceholders(Grid) + ')'; for Col := 0 to Grid.ColumnCount - 1 do - begin - Query.ParamByName('col' + IntToStr(Col)).AsString := - Grid.Cells[Col, Row]; - end; + Query.ParamByName('col' + IntToStr(Col)).AsString := Grid.Cells[Col, Row]; Query.ExecSQL; end; finally @@ -470,13 +379,10 @@ end; procedure TSettingsDatabase.SetSetting(const Name, Value: string); begin - FConnection.ExecSQL - ('INSERT OR REPLACE INTO params (name, value) VALUES (:name, :value)', - [Name, Value]); + FConnection.ExecSQL('INSERT OR REPLACE INTO params (name, value) VALUES (:name, :value)', [Name, Value]); end; -function TSettingsDatabase.ReadSetting(const Name: string; - Default: string = ''): string; +function TSettingsDatabase.ReadSetting(const Name: string; Default: string = ''): string; begin Result := GetSetting(Name); if Result = '' then @@ -488,4 +394,202 @@ begin SetSetting(Name, Value); end; +function TSettingsDatabase.TableHasColumn(const TableName, ColumnName: string): Boolean; +var + Query: TFDQuery; +begin + Query := TFDQuery.Create(nil); + try + Query.Connection := FConnection; + Query.SQL.Text := Format('PRAGMA table_info(%s)', [TableName]); + Query.Open; + Result := False; + while not Query.EOF do + begin + if SameText(Query.FieldByName('name').AsString, ColumnName) then + Exit(True); + Query.Next; + end; + finally + Query.Free; + end; +end; + +function TSettingsDatabase.GetSQLType(Field: TRttiField): string; +begin + case Field.FieldType.TypeKind of + tkInteger, tkInt64: + Result := 'INTEGER'; + tkFloat: + Result := 'REAL'; + tkUString, tkString, tkWString, tkLString: + Result := 'TEXT'; + tkEnumeration: + if Field.FieldType = TypeInfo(Boolean) then + Result := 'BOOLEAN' + else + Result := 'INTEGER'; + else + raise Exception.CreateFmt('Unsupported type for field %s', [Field.Name]); + end; +end; + +procedure TSettingsDatabase.EnsureTableForRecord(const TableName: string; RecordTypeInfo: PTypeInfo); +var + Context: TRttiContext; + RttiType: TRttiType; + Field: TRttiField; + FieldDefs: string; + Query: TFDQuery; +begin + if not CheckTableExists(TableName) then + begin + Context := TRttiContext.Create; + try + RttiType := Context.GetType(RecordTypeInfo); + FieldDefs := ''; + for Field in RttiType.GetFields do + begin + if FieldDefs <> '' then + FieldDefs := FieldDefs + ', '; + 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 + Query.Connection := FConnection; + for Field in RttiType.GetFields do + begin + if not TableHasColumn(TableName, Field.Name) then + begin + Query.SQL.Text := Format('ALTER TABLE %s ADD COLUMN %s %s', [TableName, Field.Name, GetSQLType(Field)]); + Query.ExecSQL; + end; + end; + finally + Query.Free; + end; + finally + Context.Free; + end; + end; +end; + +procedure TSettingsDatabase.SaveRecordArray(const TableName: string; const Items: array of T); +var + Context: TRttiContext; + RttiType: TRttiType; + Fields: TArray; + Query: TFDQuery; + Rec: T; + Field: TRttiField; + FieldNames, Placeholders: string; + i: Integer; +begin + if Length(Items) = 0 then Exit; + + EnsureTableForRecord(TableName, TypeInfo(T)); + FConnection.ExecSQL(Format('DELETE FROM %s', [TableName])); + + Context := TRttiContext.Create; + try + RttiType := Context.GetType(TypeInfo(T)); + Fields := RttiType.GetFields; + + Query := TFDQuery.Create(nil); + try + Query.Connection := FConnection; + + FieldNames := ''; + Placeholders := ''; + for i := 0 to High(Fields) do + begin + if i > 0 then + begin + FieldNames := FieldNames + ', '; + Placeholders := Placeholders + ', '; + end; + FieldNames := FieldNames + Fields[i].Name; + Placeholders := Placeholders + ':p_' + Fields[i].Name; + end; + + Query.SQL.Text := 'INSERT INTO '+TableName+' ('+FieldNames+') VALUES ('+Placeholders+')'; + + + for Rec in Items do + begin + Query.Params.Clear; + for Field in Fields do + Query.ParamByName('p_'+Field.Name).Value := Field.GetValue(@Rec).AsVariant; + Query.ExecSQL; + end; + finally + Query.Free; + end; + finally + Context.Free; + end; +end; + +procedure TSettingsDatabase.LoadRecordArray(const TableName: string; var Items: TArray); +var + Context: TRttiContext; + RttiType: TRttiType; + Fields: TArray; + Query: TFDQuery; + Rec: T; + RecList: TList; + Field: TRttiField; +begin + if not CheckTableExists(TableName) then + begin + SetLength(Items, 0); + Exit; + end; + + EnsureTableForRecord(TableName, TypeInfo(T)); + + Context := TRttiContext.Create; + try + RttiType := Context.GetType(TypeInfo(T)); + Fields := RttiType.GetFields; + + Query := TFDQuery.Create(nil); + RecList := TList.Create; + try + Query.Connection := FConnection; + Query.SQL.Text := Format('SELECT * FROM %s', [TableName]); + Query.Open; + + while not Query.EOF do + begin + Rec := Default(T); + for Field in Fields do + begin + if Query.FindField(Field.Name) <> nil then + Field.SetValue(@Rec, TValue.FromVariant(Query.FieldByName(Field.Name).Value)); + end; + RecList.Add(Rec); + Query.Next; + end; + + Items := RecList.ToArray; + finally + Query.Free; + RecList.Free; + end; + finally + Context.Free; + end; +end; + end. diff --git a/uGeneral.fmx b/uGeneral.fmx index 6c94cf4..d4301ba 100644 --- a/uGeneral.fmx +++ b/uGeneral.fmx @@ -1,4 +1,4 @@ -object Form1: TForm1 +object TTW_Bot: TTTW_Bot Left = 480 Top = 0 Caption = 'Form1' @@ -54,7 +54,7 @@ object Form1: TForm1 Text = #1053#1072#1089#1090#1088#1086#1081#1082#1080 ExplicitSize.cx = 96.000000000000000000 ExplicitSize.cy = 26.000000000000000000 - inline fSettings: TfrSettings + inline frSettings1: TfrSettings Align = Client Size.Width = 1042.000000000000000000 Size.Height = 718.000000000000000000 @@ -64,6 +64,10 @@ object Form1: TForm1 Images = ImageList1 ImageIndex = 10 end + inherited btnGetClientID: TButton + Images = ImageList1 + ImageIndex = 10 + end inherited btnOpenStream: TButton Images = ImageList1 ImageIndex = 17 @@ -77,9 +81,6 @@ object Form1: TForm1 inherited edtBotTokenStreamer: TEdit TabOrder = 35 end - inherited Label53: TLabel - TabOrder = 37 - end end inherited GroupBox22: TGroupBox inherited btnDAGetCode: TButton @@ -115,8 +116,10 @@ object Form1: TForm1 Images = ImageList1 ImageIndex = 18 TabOrder = 44 + OnClick = frSettings1btnDAStartClick end inherited btnGetDADef: TButton + Images = ImageList1 TabOrder = 45 end end @@ -202,8 +205,8 @@ object Form1: TForm1 Size.Height = 718.000000000000000000 Size.PlatformDefault = False inherited sgCommands: TStringGrid - Viewport.Width = 602.000000000000000000 - Viewport.Height = 168.000000000000000000 + Viewport.Width = 606.000000000000000000 + Viewport.Height = 193.000000000000000000 inherited scCommand: TStringColumn Size.Width = 134.000000000000000000 end @@ -212,56 +215,84 @@ object Form1: TForm1 end end inherited GroupBox1: TGroupBox + inherited Label12: TLabel + TabOrder = 37 + end + inherited edtCommand: TEdit + TabOrder = 38 + end inherited mResponse: TMemo - Viewport.Width = 372.000000000000000000 - Viewport.Height = 173.000000000000000000 + TabOrder = 39 + Viewport.Width = 376.000000000000000000 + Viewport.Height = 177.000000000000000000 + end + inherited Label14: TLabel + TabOrder = 40 end inherited GroupBox7: TGroupBox + TabOrder = 41 inherited btnAddUserName: TButton Images = ImageList1 ImageIndex = 11 + TabOrder = 37 end inherited btnGetDateFollow: TButton Images = ImageList1 ImageIndex = 15 + TabOrder = 38 end inherited btnGetAgeAccaunt: TButton Images = ImageList1 ImageIndex = 15 + TabOrder = 39 end inherited btnCounterAddtoText: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 40 + end + inherited cbCounterName: TComboBox + TabOrder = 41 end inherited btnGPT: TButton Images = ImageList1 ImageIndex = 19 + TabOrder = 42 end inherited btnRandomUserName: TButton Images = ImageList1 ImageIndex = 11 + TabOrder = 43 end inherited btnGetChannelStat: TButton Images = ImageList1 ImageIndex = 20 Size.Width = 128.000000000000000000 + TabOrder = 44 end inherited btnAIPic: TButton Images = ImageList1 ImageIndex = 5 + TabOrder = 45 end end inherited btnAddCommand: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 42 end inherited btnEditCommand: TButton Images = ImageList1 ImageIndex = 3 + TabOrder = 43 end inherited btnRmCommand: TButton Images = ImageList1 ImageIndex = 4 + TabOrder = 44 + end + inherited cbTextToSpeech: TCheckBox + TabOrder = 45 end end inherited GroupBox9: TGroupBox @@ -281,10 +312,18 @@ object Form1: TForm1 inherited btnRandomDel: TButton Images = ImageList1 ImageIndex = 12 + TabOrder = 37 end inherited btnRmGroup: TButton Images = ImageList1 ImageIndex = 4 + TabOrder = 38 + end + inherited Label4: TLabel + TabOrder = 39 + end + inherited Label5: TLabel + TabOrder = 41 end end inherited GroupBox8: TGroupBox @@ -292,6 +331,7 @@ object Form1: TForm1 Images = ImageList1 ImageIndex = 0 TabOrder = 32 + OnClick = frCommands1btnRandAddClick end inherited btnRandDel: TButton Images = ImageList1 @@ -299,8 +339,8 @@ object Form1: TForm1 end inherited sgRandomInt: TStringGrid TabOrder = 35 - Viewport.Width = 153.000000000000000000 - Viewport.Height = 119.000000000000000000 + Viewport.Width = 157.000000000000000000 + Viewport.Height = 144.000000000000000000 inherited scRIntName: TStringColumn Size.Width = 70.000000000000000000 end @@ -316,23 +356,37 @@ object Form1: TForm1 inherited btnSoundAdd: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 37 end inherited btnSoundDel: TButton Images = ImageList1 ImageIndex = 12 + TabOrder = 38 + end + inherited edtSoundName: TEdit + TabOrder = 39 + end + inherited edtSoundFileName: TEdit + TabOrder = 40 end inherited btnSoundOpen: TButton Images = ImageList1 ImageIndex = 14 + TabOrder = 41 Text = '' end + inherited tbSoundVolume: TTrackBar + TabOrder = 42 + end inherited btnSoundTest: TButton Images = ImageList1 ImageIndex = 25 + TabOrder = 43 end inherited sgSAFiles: TStringGrid - Viewport.Width = 341.000000000000000000 - Viewport.Height = 124.000000000000000000 + TabOrder = 44 + Viewport.Width = 345.000000000000000000 + Viewport.Height = 149.000000000000000000 inherited sgSAFile: TStringColumn Size.Width = 220.000000000000000000 end @@ -342,19 +396,29 @@ object Form1: TForm1 inherited btnTextAdd: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 37 end inherited btnTextDel: TButton Images = ImageList1 ImageIndex = 12 + TabOrder = 38 + end + inherited edtTextName: TEdit + TabOrder = 39 + end + inherited edtTextFileName: TEdit + TabOrder = 40 end inherited btnTextOpen: TButton Images = ImageList1 ImageIndex = 14 + TabOrder = 41 Text = '' end inherited sgTFiles: TStringGrid - Viewport.Width = 293.000000000000000000 - Viewport.Height = 124.000000000000000000 + TabOrder = 44 + Viewport.Width = 297.000000000000000000 + Viewport.Height = 149.000000000000000000 inherited scTFileFile: TStringColumn Size.Width = 170.000000000000000000 end @@ -367,23 +431,33 @@ object Form1: TForm1 inherited GroupBox2: TGroupBox TabOrder = 7 inherited sgAIGen: TStringGrid - Viewport.Width = 301.000000000000000000 - Viewport.Height = 124.000000000000000000 + TabOrder = 37 + Viewport.Width = 305.000000000000000000 + Viewport.Height = 149.000000000000000000 inherited StringColumn2: TStringColumn Size.Width = 180.000000000000000000 end end + inherited edtAIGenName: TEdit + TabOrder = 38 + end + inherited edtAIGenRequest: TEdit + TabOrder = 39 + end inherited btnAIGenAdd: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 40 end inherited btnAIGenDel: TButton Images = ImageList1 ImageIndex = 12 + TabOrder = 41 end inherited btnAIGetTextUser: TButton Images = ImageList1 ImageIndex = 11 + TabOrder = 42 end end end @@ -427,8 +501,8 @@ object Form1: TForm1 inherited sgWebChats: TStringGrid Size.Width = 1042.000000000000000000 Size.Height = 282.000000000000000000 - Viewport.Width = 1038.000000000000000000 - Viewport.Height = 257.000000000000000000 + Viewport.Width = 1042.000000000000000000 + Viewport.Height = 282.000000000000000000 inherited StringColumn2: TStringColumn Size.Width = 200.000000000000000000 end @@ -448,7 +522,7 @@ object Form1: TForm1 TabOrder = 3 end inherited Label1: TLabel - TabOrder = 5 + TabOrder = 6 end object btnCreateChat: TButton [4] Images = ImageList1 @@ -465,10 +539,16 @@ object Form1: TForm1 end inherited btnCreateOBSNotify: TButton Images = ImageList1 - ImageIndex = 5 + ImageIndex = 24 Position.X = 110.000000000000000000 OnClick = frOBS1btnCreateOBSNotifyClick end + inherited btnCreateOBSKandinsky: TButton + Images = ImageList1 + ImageIndex = 5 + Position.X = 264.000000000000000000 + OnClick = frOBS1btnCreateOBSKandinskyClick + end end end object TabItem6: TTabItem @@ -531,6 +611,9 @@ object Form1: TForm1 ImageIndex = 14 Text = '' end + inherited OpenDialog1: TOpenDialog + Top = 32 + end end end object TabItem7: TTabItem @@ -22198,7 +22281,7 @@ object Form1: TForm1 SourceRect.Bottom = 512.000000000000000000 end> end> - Left = 344 - Top = 194 + Left = 520 + Top = 42 end end diff --git a/uGeneral.pas b/uGeneral.pas index e97ecf6..315e879 100644 --- a/uGeneral.pas +++ b/uGeneral.pas @@ -13,13 +13,13 @@ uses windows, System.Skia, FMX.Skia, uCreateChat, uCreateNotify, fOBS; type - TForm1 = class(TForm) + TTTW_Bot = class(TForm) V: TTabControl; TabItem1: TTabItem; TabItem2: TTabItem; TabItem3: TTabItem; TabItem4: TTabItem; - fSettings: TfrSettings; + frSettings1: TfrSettings; ImageList1: TImageList; TabItem5: TTabItem; Panel1: TPanel; @@ -59,6 +59,9 @@ type procedure SpeedButton2Click(Sender: TObject); procedure frChatOBS1btnCreateChatClick(Sender: TObject); procedure frOBS1btnCreateOBSNotifyClick(Sender: TObject); + procedure frOBS1btnCreateOBSKandinskyClick(Sender: TObject); + procedure frSettings1btnDAStartClick(Sender: TObject); + procedure frCommands1btnRandAddClick(Sender: TObject); private { Private declarations } procedure ReadDB(); @@ -67,7 +70,7 @@ type end; var - Form1: TForm1; + TTW_Bot: TTTW_Bot; myConst: TConst; db: TSettingsDatabase; appconst: TBotAppCfg; @@ -76,7 +79,7 @@ implementation {$R *.fmx} -procedure TForm1.cbThemeChange(Sender: TObject); +procedure TTTW_Bot.cbThemeChange(Sender: TObject); begin cbTheme.ItemIndex := cbTheme.Items.IndexOf(cbTheme.text); if cbTheme.ItemIndex <> -1 then @@ -84,7 +87,7 @@ begin // db.WriteSetting('cbTheme', inttostr(cbTheme.ItemIndex)); end; -procedure TForm1.FormCreate(Sender: TObject); +procedure TTTW_Bot.FormCreate(Sender: TObject); var Path: string; SearchRec: TSearchRec; @@ -141,17 +144,46 @@ begin end; -procedure TForm1.frChatOBS1btnCreateChatClick(Sender: TObject); +procedure TTTW_Bot.frChatOBS1btnCreateChatClick(Sender: TObject); begin fCreateChat.Show; end; -procedure TForm1.frOBS1btnCreateOBSNotifyClick(Sender: TObject); +procedure TTTW_Bot.frCommands1btnRandAddClick(Sender: TObject); +begin + frCommands1.btnRandAddClick(Sender); + +end; + +procedure TTTW_Bot.frOBS1btnCreateOBSKandinskyClick(Sender: TObject); +var dport:integer; + I: Integer; +begin +dport:=8080; + for I := 0 to frOBS1.sgWebChats.RowCount-1 do + begin + if strtoint(frOBS1.sgWebChats.Cells[0,i]) >= dport then + dport:=strtoint(frOBS1.sgWebChats.Cells[0,i])+1; + end; + frOBS1.sgWebChats.RowCount:=frOBS1.sgWebChats.RowCount+1; + frOBS1.sgWebChats.Cells[0,frOBS1.sgWebChats.RowCount-1]:=inttostr(dport); + frOBS1.sgWebChats.Cells[1,frOBS1.sgWebChats.RowCount-1]:='Kandinsky'; + frOBS1.sgWebChats.Cells[2,frOBS1.sgWebChats.RowCount-1]:='http://127.0.0.1:'+inttostr(dport); + +end; + +procedure TTTW_Bot.frOBS1btnCreateOBSNotifyClick(Sender: TObject); begin fCreateNotify.Show; end; -procedure TForm1.ReadDB; +procedure TTTW_Bot.frSettings1btnDAStartClick(Sender: TObject); +begin + frSettings1.btnDAStartClick(Sender); + +end; + +procedure TTTW_Bot.ReadDB; var I: Integer; c: TComponent; @@ -213,25 +245,26 @@ var I: Integer; c: TComponent; begin - for I := 0 to fSettings.ComponentCount - 1 do + for I := 0 to frSettings1.ComponentCount - 1 do begin - c := fSettings.Components[I]; + c := frSettings1.Components[I]; if c is TEdit then TEdit(c).text := db.ReadSetting(TEdit(c).Name) else if c is TCheckBox then TCheckBox(c).IsChecked := (db.ReadSetting(TCheckBox(c).Name) = 'True'); end; - db.FChannel := fSettings.edtChannel.text; + db.FChannel := frSettings1.edtChannel.text; end; // Загрузка данных в гриды команд procedure LoadGridsData; begin - db.LoadGridFromTable('sgRandomInt', frCommands1.sgRandomInt); + DB.LoadRecordArray('RandomCounters', frCommands1.RandomCounters); + { db.LoadGridFromTable('sgRandomInt', frCommands1.sgRandomInt); db.LoadGridFromTable('sgCommands', frCommands1.sgCommands); db.LoadGridFromTable('sgSAFiles', frCommands1.sgSAFiles); db.LoadGridFromTable('sgTFiles', frCommands1.sgTFiles); - db.LoadGridFromTable('sgAIGen', frCommands1.sgAIGen); + db.LoadGridFromTable('sgAIGen', frCommands1.sgAIGen); } end; // Загрузка списка групп @@ -282,21 +315,17 @@ var end; end; - fSettings.btnGetClientID.Visible := (appconst.TTV_ClientID <> ''); + frSettings1.btnGetClientID.Visible := (appconst.TTV_ClientID <> ''); frAI1.btnGetAIDef.Visible := ((appconst.AI_GigaChat_AC <> '') and (appconst.AI_GigaChat_ClientID <> '')) or (appconst.AI_ChatGPT_Token <> '') or (appconst.AI_DeepSeec_Token <> ''); - fSettings.btnGetDADef.Visible := (appconst.DA_ClientID <> '') and + frSettings1.btnGetDADef.Visible := (appconst.DA_ClientID <> '') and (appconst.DA_Sicret <> '') and (appconst.DA_URL <> ''); finally sl.Free; end; end; - - - - // Загрузка настроек уведомлений procedure LoadNotifySettings; var @@ -318,9 +347,6 @@ var end; end; - - - // Загрузка настроек ИИ procedure LoadAISettings; var @@ -399,6 +425,8 @@ var 3: SetupCustomAISettings; end; + + frSettings1.init; end; @@ -423,20 +451,20 @@ begin LoadAutoActionsGrids; end; -procedure TForm1.SpeedButton1Click(Sender: TObject); +procedure TTTW_Bot.SpeedButton1Click(Sender: TObject); begin ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/incadence'), nil, nil, 1); end; -procedure TForm1.SpeedButton2Click(Sender: TObject); +procedure TTTW_Bot.SpeedButton2Click(Sender: TObject); begin // https://www.twitch.tv/kuznecogr ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/kuznecogr'), nil, nil, 1); end; -procedure TForm1.SpeedButton3Click(Sender: TObject); +procedure TTTW_Bot.SpeedButton3Click(Sender: TObject); begin // https://www.flaticon.com/ru/authors/karacis ShellExecute(0, 'open', diff --git a/uQ.fmx b/uQ.fmx new file mode 100644 index 0000000..9ef974d --- /dev/null +++ b/uQ.fmx @@ -0,0 +1,38 @@ +object frmQ: TfrmQ + Left = 0 + Top = 0 + BorderStyle = ToolWindow + Caption = #1042#1074#1077#1076#1080#1090#1077' '#1079#1085#1072#1095#1077#1085#1080#1077 + ClientHeight = 96 + ClientWidth = 466 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + DesignerMasterStyle = 0 + object Button1: TButton + Position.X = 378.000000000000000000 + Position.Y = 66.000000000000000000 + TabOrder = 0 + Text = #1054#1082 + TextSettings.Trimming = None + OnClick = Button1Click + end + object Label1: TLabel + Position.X = 8.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 450.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = #1048#1084#1103' '#1073#1086#1090#1072 + TabOrder = 1 + end + object Edit1: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 2 + Position.X = 8.000000000000000000 + Position.Y = 33.000000000000000000 + Size.Width = 450.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end +end diff --git a/uQ.pas b/uQ.pas new file mode 100644 index 0000000..49f9b31 --- /dev/null +++ b/uQ.pas @@ -0,0 +1,46 @@ +unit uQ; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Edit, + FMX.StdCtrls, FMX.Controls.Presentation; + +type + TfrmQ = class(TForm) + Button1: TButton; + Label1: TLabel; + Edit1: TEdit; + procedure Button1Click(Sender: TObject); + private + { Private declarations } + public + function GetEditText: string; + procedure SetLabelText(const Text: string); + end; + +var + frmQ: TfrmQ; + +implementation + +{$R *.fmx} + +procedure TfrmQ.Button1Click(Sender: TObject); +begin + ModalResult := mrOk; +end; + +function TfrmQ.GetEditText: string; +begin + Result := Edit1.Text; + Edit1.Text:=''; +end; + +procedure TfrmQ.SetLabelText(const Text: string); +begin + Label1.Text := Text; +end; + +end. diff --git a/uRecords.pas b/uRecords.pas index dde8e46..094887d 100644 --- a/uRecords.pas +++ b/uRecords.pas @@ -3,6 +3,13 @@ unit uRecords; interface +type + TRandomCounters = record + Name: string; + Ot: Integer; + ToValue: Integer; + end; + type TConst = record GeneralPath: string; diff --git a/uShowText.fmx b/uShowText.fmx new file mode 100644 index 0000000..bdb0978 --- /dev/null +++ b/uShowText.fmx @@ -0,0 +1,39 @@ +object fShowText: TfShowText + Left = 0 + Top = 0 + Caption = 'fShowText' + ClientHeight = 295 + ClientWidth = 498 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + DesignerMasterStyle = 0 + object Memo1: TMemo + Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] + DataDetectorTypes = [] + Align = Client + Size.Width = 498.000000000000000000 + Size.Height = 262.000000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + Viewport.Width = 494.000000000000000000 + Viewport.Height = 258.000000000000000000 + end + object Panel1: TPanel + Align = Bottom + Position.Y = 262.000000000000000000 + Size.Width = 498.000000000000000000 + Size.Height = 33.000000000000000000 + Size.PlatformDefault = False + TabOrder = 1 + object Label1: TLabel + Position.X = 8.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 345.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = #1057#1082#1086#1087#1080#1088#1091#1081' '#1101#1090#1091' '#1089#1089#1099#1083#1082#1091' '#1080' '#1086#1090#1082#1088#1086#1081' '#1074' '#1073#1088#1072#1091#1079#1077#1088#1077' '#1089' '#1072#1082#1082#1072#1091#1085#1090#1086#1084' '#1073#1086#1090#1072 + TabOrder = 1 + end + end +end diff --git a/uShowText.pas b/uShowText.pas new file mode 100644 index 0000000..4e00577 --- /dev/null +++ b/uShowText.pas @@ -0,0 +1,28 @@ +unit uShowText; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types, + FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo; + +type + TfShowText = class(TForm) + Memo1: TMemo; + Panel1: TPanel; + Label1: TLabel; + private + { Private declarations } + public + { Public declarations } + end; + +var + fShowText: TfShowText; + +implementation + +{$R *.fmx} + +end. diff --git a/uTTWAPI.pas b/uTTWAPI.pas new file mode 100644 index 0000000..2f033fe --- /dev/null +++ b/uTTWAPI.pas @@ -0,0 +1,1134 @@ +unit uTTWAPI; + +interface + +uses + System.Classes, System.SysUtils, IdHTTP, IdSSLOpenSSL, System.JSON, FMX.Forms, + IdMultipartFormData, DateUtils, uDataBase, System.Generics.Collections, + uRecords; + +type + TTTW_API = class(TObject) + private + Token_api: string; + Token_api_streamer: string; + ClientID: string; + channel_name_api: string; + BotName_api: string; + FChatBadges:tlist; + function GetFollowedAtFromJson(jsonString: string): string; + + function getTTW(method: string; ClientID: string; + isStreamer: boolean = false): string; + function DelTTW(method: string; ClientID: string; + isStreamer: boolean = false): string; + function postTTW(method: string; ClientID: string; + params: TIdMultipartFormDataStream; isStreamer: boolean = false) + : string; overload; + function postTTW(method: string; ClientID: string; params: TStringStream; + isStreamer: boolean = false): string; overload; + + function patchTTW(method: string; ClientID: string; params: TStringStream; + isStreamer: boolean = false): string; overload; + public + constructor Create(Sender: TObject); + destructor Destroy; override; + procedure Init(myClient, myToken, streamerToken, Channel, + myBotName: string); + procedure getTTWStat(Channel: string; var avg_viewers: integer; + var max_viewers: integer; var hours_watched: integer; + var followers: integer; var followers_total: integer); + procedure shoutouts(id: string); + procedure SendNotify(text: string); + procedure banUser(id: string); + procedure banUserTime(id: string; aTime: integer); + procedure unBanUser(id: string); + procedure warnUser(id: string); + procedure raid(id: string); + procedure unRaid(); + + procedure setModerator(id: string); + procedure delModerator(id: string); + procedure setVIP(id: string); + procedure delVIP(id: string); + procedure getCustomReward(var acr: Tlist); + function createCustomReward(title: string; cost: string; + promt: string = ''; isUserInput: boolean = false):TCustomRevards; + procedure UpdateCustomReward(ahr: TCustomRevards); + procedure UpdateRedemptionStatus(ahr: TCustomRewardEvent); + procedure deleteCustomReward(id: string); + function getRoomAndBot():string; + function getUserbyLogin(login: string): TUser; + function getFollow(id: string): tdatetime; + function GetRoomID: string; + procedure getGlobalChatBadges(var gcb: Tlist); + procedure getCustomChatBadges(var ccb: Tlist); + procedure GetChannelEmotes(var ce: Tlist); + procedure GetGlobalEmotes(var ge: Tlist); + function ValidateTwitchToken(const TokenName, TokenValue: string; var DayOfLive:integer): Boolean; + end; + TChatBadges = TList; + TEmotesList = TList; +var + room_id: string = ''; + bot_id: string = ''; + +implementation + +uses uGeneral; + +constructor TTTW_API.Create(Sender: TObject); +begin + // Инициализация +end; + +function TTTW_API.createCustomReward(title: string; cost: string; + promt: string = ''; isUserInput: boolean = false):TCustomRevards; +var + RequestData: TStringStream; + s, s1, json: string; i:integer; + cr:TCustomRevards; JSONData:TJSONObject; JSONArray:TJSONArray; +begin + try + if room_id = '' then + room_id:=GetRoomID ; + s := ''; + s1 := ''; + if isUserInput then + s := ' "is_user_input_required":true,'; + if promt <> '' then + s1 := ' "prompt":"' + promt + '",'; + RequestData := TStringStream.Create('{ "title":"' + title + '", ' + s + s1 + + ' "cost":' + cost + ' }', CP_UTF8); + json := postTTW('channel_points/custom_rewards?broadcaster_id=' + room_id, ClientID, + RequestData, true); + if json = '' then + // fLog.toLog(1,'TTW_API','createCustomReward','Награда не создалась, запрос врнул пустой ответ'); + JSONData := TJSONObject.ParseJSONValue(JSON) as TJSONObject; + try + if Assigned(JSONData) then + begin + JSONArray := JSONData.GetValue('data'); + for i := 0 to JSONArray.Count - 1 do + begin + cr.id := JSONArray.Items[i].GetValue('id'); + cr.title := JSONArray.Items[i].GetValue('title'); + cr.promt := JSONArray.Items[i].GetValue('prompt'); + cr.cost := JSONArray.Items[i].GetValue('cost'); + cr.is_user_input_required := JSONArray.Items[i].GetValue + ('is_user_input_required'); + end; + end; + finally + JSONData.Free; + end; + result:=cr; + except + on E: Exception do + // Form1.Log(2, 'TTTW_API.createCustomReward', E.Message); + // flog.toLog(2,'TTW_API','createCustomReward',E.Message); + end; + +end; + +destructor TTTW_API.Destroy; +begin + // Освобождение ресурсов + inherited; +end; + + + +procedure TTTW_API.Init(myClient, myToken, streamerToken, Channel, + myBotName: string); +begin + ClientID := myClient; + Token_api := myToken; + Token_api_streamer := streamerToken; + channel_name_api := Channel; + BotName_api := myBotName; + +end; + +procedure TTTW_API.banUser(id: string); +var + RequestData: TStringStream; +begin + try + + if bot_id = '' then + exit; + if room_id = '' then + exit; + + RequestData := TStringStream.Create('{"data": {"user_id":"' + id + + '","reason":"no reason"}}'); + try + postTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + + bot_id, ClientID, RequestData); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.banUser', E.Message); + // flog.toLog(2,'TTW_API','banUser',E.Message); + end; +end; + +procedure TTTW_API.banUserTime(id: string; aTime: integer); +var + + RequestData: TStringStream; +begin + try + if bot_id = '' then + exit; + if room_id = '' then + exit; + + RequestData := TStringStream.Create('{"data": {"user_id":"' + id + + '","duration":' + inttostr(aTime) + '}}'); + try + postTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + + bot_id, ClientID, RequestData); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.banUserTime', E.Message); + // flog.toLog(2,'TTW_API','banUserTime',E.Message); + end; +end; + +procedure TTTW_API.deleteCustomReward(id: string); +begin + try + if Token_api_streamer = '' then + exit; + if room_id = '' then + exit; + DelTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + '&id=' + + id, ClientID, true); + except + on E: Exception do + ///Form1.Log(2, 'TTTW_API.deleteCustomReward', E.Message); + // flog.toLog(2,'TTW_API','deleteCustomReward',E.Message); + end; +end; + +procedure TTTW_API.delModerator(id: string); +begin + try + if Token_api_streamer = '' then + exit; + + if room_id = '' then + exit; + + DelTTW('moderation/moderators?broadcaster_id=' + room_id + '&user_id=' + id, + ClientID, true); + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.delModerator', E.Message); + // flog.toLog(2,'TTW_API','delModerator',E.Message); + end; +end; + +function TTTW_API.DelTTW(method, ClientID: string; isStreamer: boolean): string; +var + url: string; + response1, Token: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + if isStreamer then + Token := Token_api_streamer + else + Token := Token_api; + + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.AddValue('Client-ID', ClientID); + http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + Token); + http.Request.ContentType := 'application/json'; + url := 'https://api.twitch.tv/helix/' + method; + response1 := http.Delete(url); + Result := response1; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.DelTTW', E.Message+' ['+method+']'); + // flog.toLog(2,'TTW_API','DelTTW',E.Message+' ['+method+']'); + end; + finally + http.Free; + ssl.Free; + end; +end; + +procedure TTTW_API.delVIP(id: string); +begin + try + if Token_api_streamer = '' then + exit; + + if room_id = '' then + exit; + + DelTTW('channels/vips?broadcaster_id=' + room_id + '&user_id=' + id, + ClientID, true); + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.delVIP', E.Message); + // flog.toLog(2,'TTW_API','delVIP',E.Message); + end; +end; + +procedure TTTW_API.getCustomReward(var acr: Tlist); +var + JSON: string; + sl: TCustomRevards; + JSONData: TJSONObject; + JSONArray: TJSONArray; + i: integer; + +begin + + try + if Token_api_streamer = '' then + begin + exit; + end; + if room_id = '' then + exit; + + JSON := getTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + + '&only_manageable_rewards=true', ClientID, true); + JSONData := TJSONObject.ParseJSONValue(JSON) as TJSONObject; + try + if Assigned(JSONData) then + begin + JSONArray := JSONData.GetValue('data'); + for i := 0 to JSONArray.Count - 1 do + begin + sl.id := JSONArray.Items[i].GetValue('id'); + sl.title := JSONArray.Items[i].GetValue('title'); + sl.promt := JSONArray.Items[i].GetValue('prompt'); + sl.cost := JSONArray.Items[i].GetValue('cost'); + sl.is_user_input_required := JSONArray.Items[i].GetValue + ('is_user_input_required'); + acr.add(sl); + end; + + end; + finally + JSONData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.getCustomReward', E.Message); + // flog.toLog(2,'TTW_API','getCustomReward',E.Message); + end; + +end; + +function TTTW_API.getFollow(id: string): tdatetime; +var + s: string; +begin + s := getTTW('channels/followers?user_id=' + id + '&broadcaster_id=' + room_id, + ClientID); + Result := strToDate(GetFollowedAtFromJson(s)); +end; + +function TTTW_API.GetFollowedAtFromJson(jsonString: string): string; +var + JSON: TJSONObject; + dataArray: TJSONArray; +begin + Result := ''; + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + Result := DateToStr + (ISO8601ToDate(dataArray.Items[0].GetValue('followed_at'))); + end; + finally + JSON.Free; + end; +end; + +function TTTW_API.getTTW(method, ClientID: string; isStreamer: boolean): string; +var + url: string; + response1, Token: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + if isStreamer then + Token := Token_api_streamer + else + Token := Token_api; + + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.AddValue('Client-ID', ClientID); + http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + Token); + url := 'https://api.twitch.tv/helix/' + method; + response1 := http.Get(url); + Result := response1; + except + on E: Exception do + ////Form1.Log(2, 'TTTW_API.getTTW', E.Message+' ['+method+']'); + // flog.toLog(2,'TTW_API','getTTW',E.Message+' ['+method+']'); + end; + finally + http.Free; + ssl.Free; + end; +end; + +procedure TTTW_API.getTTWStat(Channel: string; var avg_viewers, max_viewers, + hours_watched, followers, followers_total: integer); +var + jsonObject: TJSONObject; + url: string; + response1: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + url := 'https://twitchtracker.com/api/channels/summary/' + Channel; + response1 := http.Get(url); + jsonObject := TJSONObject.ParseJSONValue(response1) as TJSONObject; + try + if Assigned(jsonObject) then + begin + avg_viewers := jsonObject.GetValue('avg_viewers'); + max_viewers := jsonObject.GetValue('max_viewers'); + hours_watched := jsonObject.GetValue('hours_watched'); + followers := jsonObject.GetValue('followers'); + followers_total := jsonObject.GetValue('followers_total'); + end; + finally + if Assigned(jsonObject) then + jsonObject.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.getTTWStat', E.Message); + // flog.toLog(2,'TTW_API','getTTWStat',E.Message); + end; + finally + + http.Free; + ssl.Free; + end; +end; + +function ParseBadgeVersion(VersionObj: TJSONObject): TBadgeVersion; +begin + // Добавьте проверки на nil для каждого поля + Result.Id := VersionObj.GetValue('id').Value; + Result.ImageUrl1x := VersionObj.GetValue('image_url_1x').Value; + Result.ImageUrl2x := VersionObj.GetValue('image_url_2x').Value; + Result.ImageUrl4x := VersionObj.GetValue('image_url_4x').Value; + Result.Title := VersionObj.GetValue('title').Value; + Result.Description := VersionObj.GetValue('description').Value; + Result.ClickAction := VersionObj.GetValue('click_action').Value; + Result.ClickUrl := VersionObj.GetValue('click_url').Value; +end; + +function ParseChatBadge(BadgeObj: TJSONObject): TChatBadge; +var + VersionsArray: TJSONArray; + I: Integer; +begin + // Добавьте проверки на nil для каждого поля + Result.SetId := BadgeObj.GetValue('set_id').Value; + + VersionsArray := BadgeObj.GetValue('versions') as TJSONArray; + SetLength(Result.Versions, VersionsArray.Count); + + for I := 0 to VersionsArray.Count - 1 do + Result.Versions[I] := ParseBadgeVersion(VersionsArray.Items[I] as TJSONObject); +end; + +procedure ParseBadgesFromApi(const JSONResponse: string; Badges: TChatBadges); +var + RootObj: TJSONObject; + DataArray: TJSONArray; + I: Integer; + Badge: TChatBadge; +begin + + RootObj := TJSONObject.ParseJSONValue(JSONResponse) as TJSONObject; + try + DataArray := RootObj.GetValue('data') as TJSONArray; + + for I := 0 to DataArray.Count - 1 do + begin + Badge := ParseChatBadge(DataArray.Items[I] as TJSONObject); + Badges.Add(Badge); + end; + finally + RootObj.Free; + end; +end; + +procedure TTTW_API.getGlobalChatBadges(var gcb: Tlist); +var + jsonString: string; + global:TChatBadge; +begin + JSONString:=getTTW('chat/badges/global',ClientID,false); + ParseBadgesFromApi(JSONString,gcb); +end; + + + +procedure TTTW_API.getCustomChatBadges(var ccb: Tlist); +var + jsonString: string; +begin + JSONString:=getTTW('chat/badges?broadcaster_id='+room_id,ClientID,false); + ParseBadgesFromApi(JSONString,ccb); +end; + +function TTTW_API.getRoomAndBot():string; +var + jsonString: string; + JSON: TJSONObject; + dataArray: TJSONArray; +begin +try + jsonString := getTTW('users?login=' + LowerCase(BotName_api), ClientID); + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + begin + bot_id := dataArray.Items[0].GetValue('id'); + end; + end; + finally + JSON.Free; + end; +except + on E: Exception do + // fLog.toLog(2,'TTW_API','getRoomAndBot.GetBotID',e.Message); +end; +try + jsonString := getTTW('users?login=' + LowerCase(channel_name_api), ClientID); + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + begin + room_id := dataArray.Items[0].GetValue('id'); + end; + end; + finally + JSON.Free; + end; +except + on E: Exception do + // fLog.toLog(2,'TTW_API','getRoomAndBot.GetRoomID',e.Message); +end; + result:=room_id; +end; + +function TTTW_API.GetRoomID: string; +begin + + Result := room_id; +end; + +function TTTW_API.getUserbyLogin(login: string): TUser; +var + u: TUser; + JSON: TJSONObject; + dataArray: TJSONArray; + jsonString: string; +begin + jsonString := getTTW('users?login=' + LowerCase(login), ClientID); + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + begin + u.id := dataArray.Items[0].GetValue('id'); + u.login := dataArray.Items[0].GetValue('login'); + u.DisplayName := dataArray.Items[0].GetValue('display_name'); + u.created_at := ISO8601ToDate + (dataArray.Items[0].GetValue('created_at')); + end; + end; + finally + JSON.Free; + end; + Result := u; +end; + +function TTTW_API.postTTW(method, ClientID: string; + params: TIdMultipartFormDataStream; isStreamer: boolean): string; +var + url: string; + response1, Token: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + if isStreamer then + Token := Token_api_streamer + else + Token := Token_api; + + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.AddValue('Client-ID', ClientID); + http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + Token); + http.Request.ContentType := 'application/json'; + url := 'https://api.twitch.tv/helix/' + method; + response1 := http.Post(url, params); + Result := response1; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.postTTW', E.Message+' ['+method+']'); + // flog.toLog(2,'TTW_API','postTTW',E.Message+' ['+method+']'); + end; + finally + http.Free; + ssl.Free; + end; +end; + +function TTTW_API.patchTTW(method, ClientID: string; params: TStringStream; + isStreamer: boolean): string; +var + url: string; + response1, Token: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + if isStreamer then + Token := Token_api_streamer + else + Token := Token_api; + + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.AddValue('Client-ID', ClientID); + http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + Token); + http.Request.ContentType := 'application/json'; + url := 'https://api.twitch.tv/helix/' + method; + response1 := http.Patch(url, params); + Result := response1; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.patchTTW', E.Message+' ['+method+']'); + //flog.toLog(2,'TTW_API','patchTTW',E.Message+' ['+method+']'); + end; + finally + http.Free; + ssl.Free; + end; +end; + +function TTTW_API.postTTW(method, ClientID: string; params: TStringStream; + isStreamer: boolean): string; +var + url: string; + response1, Token: string; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + try + if isStreamer then + Token := Token_api_streamer + else + Token := Token_api; + + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.AddValue('Client-ID', ClientID); + http.Request.CustomHeaders.AddValue('Authorization', 'Bearer ' + Token); + http.Request.ContentType := 'application/json'; + url := 'https://api.twitch.tv/helix/' + method; + response1 := http.Post(url, params); + Result := response1; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.postTTW', E.Message+' ['+method+']'); + //flog.toLog(2,'TTW_API','postTTW',E.Message+' ['+method+']'); + end; + finally + http.Free; + ssl.Free; + end; +end; + +procedure TTTW_API.raid(id: string); +var + p: TIdMultipartFormDataStream; +begin + try + + if room_id = '' then + exit; + + p := TIdMultipartFormDataStream.Create; + try + postTTW('raids?from_broadcaster_id=' + room_id + '&to_broadcaster_id=' + + id, ClientID, p, true); + finally + p.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.raid', E.Message); + // flog.toLog(2,'TTW_API','raid',E.Message); + end; +end; + +procedure TTTW_API.SendNotify(text: string); +var + p: TStringStream; +begin + try + if bot_id = '' then + begin + //flog.toLog(1,'TTW_API','SendNotify','bot_id пуст. Исправляю'); + getRoomAndBot; + if bot_id = '' then + begin + // flog.toLog(2,'TTW_API','SendNotify','bot_id все равно пуст'); + exit; + end; + end; + if room_id = '' then + begin + // flog.toLog(2,'TTW_API','SendNotify','room_id пуст'); + exit; + end; + + p := TStringStream.Create('{"message":"' + text + + '","color":"primary"}', CP_UTF8); + try + postTTW('chat/announcements?broadcaster_id=' + room_id + '&moderator_id=' + + bot_id, ClientID, p); + finally + p.Free; + end; + except + on E: Exception do + // flog.toLog(2,'TTW_API','SendNotify',E.Message); + end; +end; + +procedure TTTW_API.setModerator(id: string); +var + RequestData: TStringStream; +begin + try + if Token_api_streamer = '' then + exit; + if room_id = '' then + exit; + + RequestData := TStringStream.Create(''); + try + postTTW('moderation/moderators?broadcaster_id=' + room_id + '&user_id=' + + id, ClientID, RequestData, true); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.setModerator', E.Message); + // flog.toLog(2,'TTW_API','setModerator',E.Message); + end; +end; + +procedure TTTW_API.setVIP(id: string); +var + userID: string; + RequestData: TStringStream; +begin + try + if Token_api_streamer = '' then + exit; + if room_id = '' then + exit; + + RequestData := TStringStream.Create(''); + try + postTTW('channels/vips?broadcaster_id=' + room_id + '&user_id=' + userID, + ClientID, RequestData, true); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.setVIP', E.Message); + // flog.toLog(2,'TTW_API','setVIP',E.Message); + end; +end; + +procedure TTTW_API.shoutouts(id: string); +var + p: TIdMultipartFormDataStream; +begin + try + if bot_id = '' then + exit; + if room_id = '' then + exit; + + p := TIdMultipartFormDataStream.Create; + try + postTTW('chat/shoutouts?from_broadcaster_id=' + room_id + + '&to_broadcaster_id=' + id + '&moderator_id=' + bot_id, ClientID, p); + finally + p.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.shoutouts', E.Message); + // flog.toLog(2,'TTW_API','shoutouts',E.Message); + end; +end; + +procedure TTTW_API.unBanUser(id: string); +begin + try + if bot_id = '' then + exit; + if room_id = '' then + exit; + + DelTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + + bot_id + '&user_id=' + id, ClientID); + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.unBanUser', E.Message); + // flog.toLog(2,'TTW_API','unBanUser',E.Message); + end; +end; + +procedure TTTW_API.unRaid; +begin + try + if room_id = '' then + exit; + + DelTTW('raids?broadcaster_id=' + room_id, ClientID); + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.unRaid', E.Message); + // flog.toLog(2,'TTW_API','unRaid',E.Message); + end; +end; + +procedure TTTW_API.UpdateCustomReward(ahr: TCustomRevards); +var + RequestData: TStringStream; + qid: string; +begin + try + if room_id = '' then + exit; + qid := ahr.id; + RequestData := TStringStream.Create('{"cost": ' + inttostr(ahr.cost) + + '}', CP_UTF8); + try + patchTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + + '&id=' + qid, ClientID, RequestData, true); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.UpdateCustomReward', E.Message); + // flog.toLog(2,'TTW_API','UpdateCustomReward',E.Message); + end; +end; + +procedure TTTW_API.UpdateRedemptionStatus(ahr: TCustomRewardEvent); +var + qbid, qrid, qid: string; + RequestData: TStringStream; +begin + try + if bot_id = '' then + exit; + if room_id = '' then + exit; + qbid := ahr.event.broadcaster_user_id; + qrid := ahr.event.revard.id; + qid := ahr.event.id; + //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); + RequestData := TStringStream.Create('{"status":"CANCELED"}', CP_UTF8); + try + patchTTW('channel_points/custom_rewards/redemptions?broadcaster_id=' + + qbid + '&reward_id=' + qrid + '&id=' + qid, ClientID, + RequestData, true); + finally + RequestData.Free; + end; + except + on E: Exception do + // Form1.Log(2, 'TTTW_API.UpdateRedemptionStatus', E.Message); + // flog.toLog(2,'TTW_API','UpdateRedemptionStatus',E.Message); + end; +end; + +function TTTW_API.ValidateTwitchToken(const TokenName, + TokenValue: string; var DayOfLive:integer): Boolean; +var + HTTP: TIdHTTP; + SSLHandler: TIdSSLIOHandlerSocketOpenSSL; + ResponseStream: TStringStream; + ResponseJSON: TJSONObject; + StatusCode: Integer; + ResponseText: string; +begin + Result := False; + if Trim(TokenValue) = '' then + begin + Exit; + end; + + HTTP := TIdHTTP.Create(nil); + SSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); + ResponseStream := TStringStream.Create; + try + HTTP.IOHandler := SSLHandler; + HTTP.Request.CustomHeaders.Values['Authorization'] := 'OAuth ' + TokenValue; + HTTP.Request.UserAgent := 'YourApp/1.0'; + HTTP.Request.Accept := 'application/json'; + HTTP.HTTPOptions := [hoKeepOrigProtocol]; + + try + HTTP.Get('https://id.twitch.tv/oauth2/validate', ResponseStream); + StatusCode := HTTP.ResponseCode; + ResponseText := ResponseStream.DataString; + except + on E: EIdHTTPProtocolException do + begin + StatusCode := E.ErrorCode; + ResponseText := E.ErrorMessage; + end; + on E: Exception do + begin + Exit; + end; + end; + + if StatusCode = 200 then + begin + try + ResponseJSON := TJSONObject.ParseJSONValue(ResponseText) as TJSONObject; + 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])); + DayOfLive:=round(ResponseJSON.GetValue('expires_in').Value.ToInteger/60/60/24); + end; + Result := True; + finally + ResponseJSON.Free; + end; + except + on E: Exception do + //fLog.toLog(2, 'TokenCheck', 'JSON Parse', E.Message); + end; + end + else if StatusCode = 401 then + begin + //fLog.toLog(2, 'TokenCheck', TokenName, 'Invalid token'); + DayOfLive:=0; + end + else + begin + DayOfLive:=0; + // fLog.toLog(2, 'TokenCheck', TokenName, + // Format('HTTP %d: %s', [StatusCode, ResponseText])); + end; + + finally + ResponseStream.Free; + SSLHandler.Free; + HTTP.Free; + end; +end; + +procedure TTTW_API.warnUser(id: string); +var + RequestData: TStringStream; +begin + try + if bot_id = '' then + exit; + if room_id = '' then + exit; + + RequestData := TStringStream.Create('{"data": {"user_id":"' + id + + '","reason":"Вам вынесено предупреждение!"}}', CP_UTF8); + try + postTTW('moderation/warnings?broadcaster_id=' + room_id + '&moderator_id=' + + bot_id, ClientID, RequestData); + finally + RequestData.Free; + end; + except + on E: Exception do + //Form1.Log(2, 'TTTW_API.warnUser', E.Message); + // flog.toLog(2,'TTW_API','warnUser',E.Message); + end; +end; + + +// Вспомогательные функции для парсинга +function GetStringValue(JSONObj: TJSONObject; const Name: string): string; +var + Val: TJSONValue; +begin + Val := JSONObj.GetValue(Name); + if Assigned(Val) then + Result := Val.Value + else + Result := ''; +end; + +function GetStringArray(JSONObj: TJSONObject; const Name: string): TArray; +var + Arr: TJSONArray; + I: Integer; +begin + SetLength(Result, 0); + if not JSONObj.TryGetValue(Name, Arr) then Exit; + + SetLength(Result, Arr.Count); + for I := 0 to Arr.Count - 1 do + Result[I] := Arr.Items[I].Value; +end; + +// Функция для преобразования JSON в список эмодзи +procedure ParseEmotes(const JSONString: string; EmotesList: TEmotesList); +var + RootObj: TJSONObject; + DataArr: TJSONArray; + EmoteObj: TJSONObject; + ImagesObj: TJSONObject; + Emote: TEmotes; + I: Integer; +begin + + RootObj := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + try + if not RootObj.TryGetValue('data', DataArr) then Exit; + + for I := 0 to DataArr.Count - 1 do + begin + EmoteObj := DataArr.Items[I] as TJSONObject; + + // Заполняем основную информацию + Emote.id := GetStringValue(EmoteObj, 'id'); + Emote.name := GetStringValue(EmoteObj, 'name'); + Emote.tier := GetStringValue(EmoteObj, 'tier'); + Emote.emote_type := GetStringValue(EmoteObj, 'emote_type'); + Emote.emote_set_id := GetStringValue(EmoteObj, 'emote_set_id'); + + // Парсим изображения + if EmoteObj.TryGetValue('images', ImagesObj) then + begin + Emote.images.Url1x := GetStringValue(ImagesObj, 'url_1x'); + Emote.images.Url2x := GetStringValue(ImagesObj, 'url_2x'); + Emote.images.Url4x := GetStringValue(ImagesObj, 'url_4x'); + end; + + // Парсим массивы + Emote.format := GetStringArray(EmoteObj, 'format'); + Emote.scale := GetStringArray(EmoteObj, 'scale'); + Emote.theme_mode := GetStringArray(EmoteObj, 'theme_mode'); + + EmotesList.Add(Emote); + end; + finally + RootObj.Free; + end; +end; + + procedure TTTW_API.GetChannelEmotes(var ce: Tlist); +var jsonres:string; +begin + jsonres:=getTTW('chat/emotes?broadcaster_id='+room_id,ClientID,false); + ParseEmotes(jsonres, ce); +end; + +procedure TTTW_API.GetGlobalEmotes(var ge: Tlist); +var jsonres:string; +begin + jsonres:=getTTW('chat/emotes/global',ClientID,false); + ParseEmotes(jsonres, ge); +end; + +end. diff --git a/uTWAuth.pas b/uTWAuth.pas new file mode 100644 index 0000000..62b929b --- /dev/null +++ b/uTWAuth.pas @@ -0,0 +1,171 @@ +unit uTWAuth; + +interface + +uses + System.SysUtils, System.Classes, IdContext, IdCustomHTTPServer, IdHTTPServer, + IdComponent, ShellAPI; + +type + TmyEvent = procedure(txt: string) of object; + +type + TTTWAuth = class + FmyEvent: TmyEvent; + FURL: string; + private + FHTTPServer: TIdHTTPServer; + procedure HandleRequest(ASender: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); + procedure HandleRootRequest(ARequestInfo: TIdHTTPRequestInfo; + AResponseInfo: TIdHTTPResponseInfo); + procedure HandleRedirectRequest(ARequestInfo: TIdHTTPRequestInfo; + AResponseInfo: TIdHTTPResponseInfo); + procedure HandleDARequest(ARequestInfo: TIdHTTPRequestInfo; + AResponseInfo: TIdHTTPResponseInfo); + procedure OnStatus(ASender: TObject; const AStatus: TIdStatus; + const AStatusText: string); + + public + constructor Create; + destructor Destroy; override; + procedure StartServer(aURL: string); + procedure StopServer; + property OnToken: TmyEvent read FmyEvent write FmyEvent; + property OnError: TmyEvent read FmyEvent write FmyEvent; + + end; + +implementation + +constructor TTTWAuth.Create; +begin + FHTTPServer := TIdHTTPServer.Create(nil); + FHTTPServer.OnCommandGet := HandleRequest; + FHTTPServer.OnStatus := OnStatus; +end; + +destructor TTTWAuth.Destroy; +begin + FHTTPServer.Free; + inherited; +end; + +procedure TTTWAuth.StartServer(aURL: string); +begin + 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); +end; + +procedure TTTWAuth.StopServer; +begin + FHTTPServer.Active := False; + +end; + +procedure TTTWAuth.HandleRequest(ASender: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); +begin + + if ARequestInfo.Document = '/' then + HandleRootRequest(ARequestInfo, AResponseInfo) + else if ARequestInfo.Document = '/redirect' then + HandleRedirectRequest(ARequestInfo, AResponseInfo) + else if ARequestInfo.Document = '/da' then + HandleDARequest(ARequestInfo, AResponseInfo) + else + begin + AResponseInfo.ResponseNo := 404; + AResponseInfo.ContentText := 'Not Found'; + end; +end; + +procedure TTTWAuth.HandleRootRequest(ARequestInfo: TIdHTTPRequestInfo; + AResponseInfo: TIdHTTPResponseInfo); +begin + AResponseInfo.ContentText := '' + #13 + '' + #13 + + '' + #13 + ' Redirecting...' + #13 + '' + #13 + + '' + #13 + '

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

' + #13 + '' + #13 + '' + #13 + + ''; +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 + + ''; +end; + +procedure TTTWAuth.HandleRedirectRequest(ARequestInfo: TIdHTTPRequestInfo; + AResponseInfo: TIdHTTPResponseInfo); +var + i: integer; + AccessToken: string; +begin + 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 + + ''; + AResponseInfo.WriteContent; + OnToken(AccessToken); + Destroy; + end; + 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 + + ''; + AResponseInfo.WriteContent; + OnError(AccessToken); + Destroy; + end; + 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.WriteContent; + OnToken(AccessToken); + Destroy; + end; +end; + +end. diff --git a/uWSDA.pas b/uWSDA.pas new file mode 100644 index 0000000..f7b6c28 --- /dev/null +++ b/uWSDA.pas @@ -0,0 +1,155 @@ +unit uWSDA; + +interface + +uses + Classes, SysUtils, System.JSON, ipwwsclient, StrUtils, uAPIDA; + +type + TOnDonateEvent = procedure(aNick, aMessage, aSum: string) of object; + TOnStatusEvent = procedure(AStatusText: string; AStatusCode:integer) of object; + + TWSClient = class(TObject) + private + FWS: TipwWSClient; + FAPIClient: TAPIClient; + FOnDonate: TOnDonateEvent; + FOnStatus: TOnStatusEvent; + FWsstoken: string; + FWSID: 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); + function ExtractValue(const T_, Text, _T: string): string; + procedure HandleIncomingData(const Data: string); + public + constructor Create; + destructor Destroy; override; + procedure Connect(const WSSURL: string); + procedure Disconnect; + procedure Send(const Data: string); + property OnDonate: TOnDonateEvent read FOnDonate write FOnDonate; + property OnStatus: TOnStatusEvent read FOnStatus write FOnStatus; + property Wsstoken: string read FWsstoken write FWsstoken; + property WSID: string read FWSID write FWSID; + property APIClient: TAPIClient read FAPIClient write FAPIClient; + end; + +implementation + +constructor TWSClient.Create; +begin + FWS := TipwWSClient.Create(nil); + FWS.OnDataIn := DataIn; + FWS.OnConnectionStatus := ConnectionStatus; + FWS.OnError := Error; + +end; + +destructor TWSClient.Destroy; +begin + FWS.Disconnect; + FWS.Free; + inherited; +end; + +procedure TWSClient.Disconnect; +begin +FWS.Disconnect; +end; + +procedure TWSClient.Connect(const WSSURL: string); +begin + FWS.ConnectTo(WSSURL); +end; + +procedure TWSClient.Send(const Data: string); +begin + FWS.SendText(Data); +end; + +procedure TWSClient.DataIn(Sender: TObject; DataFormat: Integer; + const Text: string; const TextB: TBytes; EOM, EOL: Boolean); +begin + HandleIncomingData(Text); + // FWS.Ping; +end; + +procedure TWSClient.ConnectionStatus(Sender: TObject; + const ConnectionEvent: string; StatusCode: Integer; + const Description: string); +begin + if Assigned(FOnStatus) then + FOnStatus(ConnectionEvent,StatusCode); +end; + +procedure TWSClient.Error(Sender: TObject; ErrorCode: Integer; + const Description: string); +begin +// fLog.toLog(2, 'uWSDA', 'Error', 'Code: ' + IntToStr(ErrorCode) + ' - ' + +// Description); +end; + +function TWSClient.ExtractValue(const T_, Text, _T: string): string; +var + StartPos, EndPos: Integer; +begin + StartPos := Pos(T_, Text); + if StartPos = 0 then + Exit(''); + StartPos := StartPos + Length(T_); + EndPos := PosEx(_T, Text, StartPos); + if EndPos = 0 then + Exit(''); + Result := Copy(Text, StartPos, EndPos - StartPos); +end; + +procedure TWSClient.HandleIncomingData(const Data: string); +var + JSON: TJSONObject; + jo: TJSONObject; + DataObj: TJSONObject; + DonationData: TJSONObject; + ChannelArray: TJSONArray; + wsstoken2: string; +begin +// fLog.toLog(3, 'uWSDA', '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 + 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 }'); + end; + end; + // Обработка донатов + if Pos('"name":"Donations"', Data) > 0 then + begin + // fLog.toLog(3, 'uWSDA', 'HandleIncomingData', 'Новый Донат'); + JSON := TJSONObject.ParseJSONValue(Data) as TJSONObject; + 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')); + finally + JSON.Free; + end; + end; +end; + +end.