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.