{ получение доната procedure TfrSettings.HandleWSDonate(aNick, aMessage, aSum: string); получение сообщения procedure TTTW_Bot.ttwIRCOnMessageRecord(aRecord: TTwitchChatMessage); получение событий ttw_ES.OnFollow := frOBS1.toEventWebServer; ttw_ES.OnSub := frOBS1.toEventWebServer; ttw_ES.OnGift := frOBS1.toEventWebServer; ttw_ES.OnRaid := frOBS1.toEventWebServer; } unit uGeneral; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, DateUtils, System.Variants, uTTWIRC, uTTWEventSub, uTTWAPI, uSoundManager, uKeyEvent, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl, FMX.Controls.Presentation, FMX.StdCtrls, System.ImageList, FMX.ImgList, FMX.Styles, ShellAPI, StrUtils, IdComponent, uRegExpr, uCustomEmoties, fSettings, fAI, fNotify, fAutoActions, FMX.ListBox, fLog, fEvents, System.Generics.Collections, utts, uGigaChat, uChatAPI, uMyTimer, uRecords, System.IOUtils, fCommands, uDataBase, FMX.Edit, FMX.Colors, FMX.SpinBox, windows, System.Skia, FMX.Skia, uCreateChat, uCreateNotify, fOBS, fTTS, fPlayerWeb, uWebServerKandinsky, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, fRevards, json; type TTTW_Bot = class(TForm) V: TTabControl; TabItem1: TTabItem; TabItem2: TTabItem; TabItem3: TTabItem; TabItem4: TTabItem; frSettings1: TfrSettings; ImageList1: TImageList; TabItem5: TTabItem; Panel1: TPanel; btnConnecting: TButton; Label2: TLabel; Label3: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; Label8: TLabel; aiConnecting: TAniIndicator; Label9: TLabel; Label10: TLabel; Label11: TLabel; Label12: TLabel; frAI1: TfrAI; TabItem6: TTabItem; TabItem7: TTabItem; TabItem8: TTabItem; TabItem9: TTabItem; frNotify1: TfrNotify; Label1: TLabel; frAutoActions1: TfrAutoActions; frOBS1: TfrOBS; frLog1: TfrLog; cbTheme: TComboBox; Label15: TLabel; frCommands1: TfrCommands; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; SpeedButton3: TSpeedButton; btnCreateChat: TButton; frTTS1: TfrTTS; GroupBox1: TGroupBox; frPlayerWeb1: TfrPlayerWeb; frEvents1: TfrEvents; GroupBox2: TGroupBox; GroupBox3: TGroupBox; frRevards1: TfrRevards; procedure cbThemeChange(Sender: TObject); procedure FormCreate(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); procedure SpeedButton3Click(Sender: TObject); procedure SpeedButton2Click(Sender: TObject); procedure frSettings1btnDAStartClick(Sender: TObject); procedure frCommands1btnRandAddClick(Sender: TObject); procedure frOBS1btnDeleteeChatClick(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure frTTS1btnSendClick(Sender: TObject); procedure btnConnectingClick(Sender: TObject); procedure frAutoActions1btnCounterAddClick(Sender: TObject); procedure frAutoActions1btnCounterEditClick(Sender: TObject); procedure frAutoActions1btnCounterDeleteClick(Sender: TObject); private { Private declarations } procedure toSpeech(aText: string); procedure OnTTWStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); procedure ttwIRCOnMessageRecord(aRecord: TTwitchChatMessage); procedure PlayNotifySound(aMod, aVip, aSub: Boolean); procedure CheckBannedWords(const aText, dname, aID: string); procedure ReadDB(); function PreprocessMessageText(const aText: string): string; procedure UpdateWordCounters(const aText: string); procedure decodeResponseSendToTTW(aResponseText, aCommandText, adName: string); function Pars(T_, text, _T: string): string; procedure gptError(Msg: string); function ResponsParserGroups(inMess: string): string; function ResponsParserRandoms(inMess: string): string; function ResponsParserCounters(inMess: string): string; function ResponsParserStatic(inMess, adName, aCommandText: string): string; function ResponsParserAPI(inMess, adName: string): string; function ResponsParserGPT(inMess, aCommandText: string): string; function ResponsParserAIGen(inMess, aCommandText: string): string; function ResponsParserAIPic(inMess, aCommandText, aNick: string): string; function ResponsParserSounds(inMess: string): string; function ResponsParserText(inMess: string): string; function ValidateInput: Boolean; procedure ConnectProcedure; procedure DisconnectProcedure; procedure HandleTimers(Start: Boolean); procedure ESError(aMsg: string); procedure ESStatus(Sender: TObject; const ConnectionEvent: String; StatusCode: Integer; const Description: String); procedure ESOnSubOk(s: string); procedure checkAndSendToGames(aNick: string; aCommand: string); public { Public declarations } procedure toLog(aModule, aMethod, aMessage: string; aCode: Integer); function FindUserRow(const aNick: string): Integer; procedure GlobalExceptionHandler(Sender: TObject; E: Exception); end; var TTW_Bot: TTTW_Bot; myConst: TConst; db: TSettingsDatabase; appconst: TBotAppCfg; ttw_IRS: TTTW; ttw_ES: TTTW_ES; ttw_API: TTTW_API; userlist: TList; Kandinsky: TKandinsky_Web; kePoints: TKE; implementation {$R *.fmx} procedure TTTW_Bot.HandleTimers(Start: Boolean); var I: Integer; begin for I := 0 to high(frAutoActions1.listTimer) do begin if Assigned(frAutoActions1.FTimerList[I]) then begin if Start and (frAutoActions1.listTimer[I].Enable = 1) then frAutoActions1.FTimerList[I].StartT else frAutoActions1.FTimerList[I].StopT; end; end; end; procedure TTTW_Bot.toSpeech(aText: string); var tts: ttts; s, s1: string; begin s := ExtractFilePath(ParamStr(0)) + 'piper\piper.exe'; s1 := ExtractFilePath(ParamStr(0)) + 'piper\voices'; if frTTS1.cbVoices.ItemIndex = -1 then exit; tts := ttts.Create(s, s1); try tts.SetModel(frTTS1.cbVoices.text); tts.TextToSpeech(aText, true); finally tts.Free; end; end; procedure TTTW_Bot.GlobalExceptionHandler(Sender: TObject; E: Exception); begin try TTW_Bot.toLog('GlobalException', E.ClassName, E.Message, 2); except // на случай, если логгер сам кинет исключение end; end; procedure TTTW_Bot.ConnectProcedure; var rid: string; BotToken, StreamerToken: string; dol: Integer; begin BotToken := frSettings1.edtBotToken.text; StreamerToken := frSettings1.edtBotTokenStreamer.text; // Проверка бот-токена if not ttw_API.ValidateTwitchToken('Бот', BotToken, dol) then raise Exception.Create('Недействительный бот-токен'); Label12.text := inttostr(dol); // Проверка стример-токена (если отличается) if (BotToken <> StreamerToken) and not ttw_API.ValidateTwitchToken('Стример', StreamerToken, dol) then raise Exception.Create('Недействительный стример-токен'); Label11.text := inttostr(dol); // Инициализация и подключение основного сервиса ttw_IRS.Init(frSettings1.edtBotToken.text, frSettings1.edtChannel.text, frSettings1.edtBotName.text); ttw_IRS.Connect; // Инициализация API try ttw_API.Init(frSettings1.edtBotClientID.text, frSettings1.edtBotToken.text, IfThen(frSettings1.edtBotTokenStreamer.text = '', frSettings1.edtBotToken.text, frSettings1.edtBotTokenStreamer.text), frSettings1.edtChannel.text, frSettings1.edtBotName.text); except on E: Exception do begin toLog('uGeneral', 'ConnectProcedure.ttw_API.Init', E.Message, 2); end; end; // Обработка ролей rid := ttw_API.getRoomAndBot; if rid = '' then raise Exception.Create('Не удалось получить Room ID'); try frRevards1.LoadCustomRevards; except on E: Exception do begin toLog('uGeneral', 'ConnectProcedure.LoadCustomRevards', E.Message, 2); end; end; // Загрузка эмодзи и бейджей try frOBS1.ChatBadges.Clear; ttw_API.getCustomChatBadges(frOBS1.ChatBadges); ttw_API.getGlobalChatBadges(frOBS1.ChatBadges); frOBS1.ChatEmotes.Clear; ttw_API.GetChannelEmotes(frOBS1.ChatEmotes); ttw_API.GetGlobalEmotes(frOBS1.ChatEmotes); frOBS1.BTTV.getGlobal; frOBS1.BTTV.getCustom(rid); frOBS1.m7tv.getGlobal; frOBS1.m7tv.getCustom(rid); except on E: Exception do begin toLog('uGeneral', 'ConnectProcedure.Emotes', E.Message, 2); end; end; // Инициализация EventSub try if Assigned(ttw_ES) then FreeAndNil(ttw_ES); ttw_ES := TTTW_ES.Create(Self, frSettings1.edtBotTokenStreamer.text, frSettings1.edtBotClientID.text, rid); // Назначение обработчиков событий ttw_ES.OnLog := toLog; ttw_ES.OnError := ESError; ttw_ES.OnGetCustomReward := frEvents1.ESOnGetCustomReward; ttw_ES.OnStatus := ESStatus; // ttw_ES.OnRAW := fRewards.ESOnRAW; ttw_ES.OnSubOk := ESOnSubOk; ttw_ES.OnFollow := frOBS1.toEventWebServer; ttw_ES.OnSub := frOBS1.toEventWebServer; ttw_ES.OnGift := frOBS1.toEventWebServer; ttw_ES.OnRaid := frOBS1.toEventWebServer; toLog('uGeneral', 'ConnectProcedure.ttw_ES', 'Создан', 0); ttw_ES.Connect; except on E: Exception do begin toLog('uGeneral', 'ConnectProcedure.ttw_ES', E.Message, 2); end; end; HandleTimers(true); if frSettings1.cbDAAutoLogin.IsChecked then begin frSettings1.btnDAGetCodeClick(Self); end; end; procedure TTTW_Bot.DisconnectProcedure; begin try toLog('DisconnectProcedure', 'Start', 'Начало процедуры отключения', 3); // 1. Отключаем IRS toLog('DisconnectProcedure', 'ttw_IRS.Disconnect', 'Попытка отключения IRS', 3); ttw_IRS.Disconnect; // 2. Обрабатываем EventSub if Assigned(ttw_ES) then begin toLog('DisconnectProcedure', 'ttw_ES.Disconnect', 'Попытка отключения ES', 3); ttw_ES.Disconnect; // Даем время на корректное завершение Sleep(150); toLog('DisconnectProcedure', 'ttw_ES.Free', 'Освобождение ES', 3); FreeAndNil(ttw_ES); end; // 3. Очищаем интерфейс { if Assigned(fChatFrame) then begin fChatFrame.ChatBadges.Clear; fChatFrame.ChatEmotes.Clear; { for I := 0 to fChatFrame.ChatWebServers.Count - 1 do begin fChatFrame.ChatWebServers[I].WebServerChat.ActiveServer(false); end; end; } // 4. Останавливаем таймеры HandleTimers(false); frSettings1.FWSClient.Disconnect; Label3.text := 'Disconnected'; Label6.text := 'Disconnected'; except on E: Exception do toLog('DisconnectProcedure', 'General', E.ClassName + ': ' + E.Message, 2); end; end; procedure TTTW_Bot.ESError(aMsg: string); begin toLog('uGeneral', 'ESError', aMsg, 2); end; procedure TTTW_Bot.ESOnSubOk(s: string); begin toLog('uGeneral', 'ESOnSubOk', s, 0); end; procedure TTTW_Bot.ESStatus(Sender: TObject; const ConnectionEvent: String; StatusCode: Integer; const Description: String); begin Label6.text := ConnectionEvent; end; function TTTW_Bot.ValidateInput: Boolean; begin Result := false; if Trim(frSettings1.edtBotToken.text) = '' then begin ShowMessage('Токен бота обязателен для заполнения!'); exit; end; if Trim(frSettings1.edtChannel.text) = '' then begin ShowMessage('Название канала обязательно для заполнения!'); exit; end; if (Trim(frSettings1.edtBotClientID.text) = '') then begin ShowMessage('Client ID обязателен!'); exit; end; Result := true; end; procedure TTTW_Bot.btnConnectingClick(Sender: TObject); begin if not ValidateInput then exit; btnConnecting.Enabled := false; aiConnecting.Enabled := true; aiConnecting.Visible := true; try if btnConnecting.text = 'Подключиться' then ConnectProcedure else DisconnectProcedure; TThread.Synchronize(nil, procedure begin btnConnecting.text := IfThen(btnConnecting.text = 'Подключиться', 'Отключиться', 'Подключиться'); end); except on E: Exception do ShowMessage('Ошибка подключения: ' + E.Message); end; aiConnecting.Enabled := false; aiConnecting.Visible := false; btnConnecting.Enabled := true; end; procedure TTTW_Bot.cbThemeChange(Sender: TObject); begin cbTheme.ItemIndex := cbTheme.Items.IndexOf(cbTheme.text); if cbTheme.ItemIndex <> -1 then TStyleManager.SetStyleFromFile(myConst.stlPath + cbTheme.text); db.WriteSetting('cbTheme', inttostr(cbTheme.ItemIndex)); end; procedure TTTW_Bot.checkAndSendToGames(aNick: string; aCommand: string); var I: Integer; j: Integer; s:string; begin for I := 0 to frOBS1.GameWebServers.Count - 1 do begin for j := 0 to frOBS1.GameWebServers[I].commands.Count - 1 do begin s:= frOBS1.GameWebServers[I].commands[j]; if s= aCommand.Trim then begin var obj := TJSONObject.Create; obj.AddPair('user', aNick); obj.AddPair('cmd', aCommand.Trim); frOBS1.GameWebServers[I].WebServerGame.Input(obj); end; end; end; end; procedure TTTW_Bot.FormCreate(Sender: TObject); var Path: string; function GetPathToTestExe: string; // вернет папку romaming begin Result := GetEnvironmentVariable('APPDATA'); if Result <> '' then Result := IncludeTrailingPathDelimiter(Result); end; begin myConst.GeneralPath := ExtractFilePath(ParamStr(0)); myConst.AppDataPath := GetPathToTestExe + 'TTW_Bot\'; if not DirectoryExists(myConst.AppDataPath) then CreateDir(myConst.AppDataPath); myConst.DBPath := myConst.AppDataPath + 'settings.db'; if not DirectoryExists(myConst.AppDataPath + 'fonts') then CreateDir(myConst.AppDataPath + 'fonts'); myConst.fontsPath := myConst.AppDataPath + 'fonts\'; if not DirectoryExists(myConst.AppDataPath + 'imgs') then CreateDir(myConst.AppDataPath + 'imgs'); myConst.imgsPath := myConst.AppDataPath + 'imgs\'; if not DirectoryExists(myConst.AppDataPath + 'sounds') then CreateDir(myConst.AppDataPath + 'sounds'); myConst.soundsPath := myConst.AppDataPath + 'sounds\'; if not DirectoryExists(myConst.AppDataPath + 'stl') then CreateDir(myConst.AppDataPath + 'stl'); myConst.stlPath := myConst.AppDataPath + 'stl\'; if not DirectoryExists(myConst.AppDataPath + 'ytSongs') then CreateDir(myConst.AppDataPath + 'ytSongs'); myConst.ytSongsPath := myConst.AppDataPath + 'ytSongs\'; myConst.SilentPlay := myConst.GeneralPath + 'SilentPlayer.exe'; myConst.ytPlay := myConst.GeneralPath + 'Player.exe'; myConst.cfg1 := myConst.GeneralPath + 'botapp.cfg'; db := TSettingsDatabase.Create(myConst.DBPath); frAutoActions1.FTimerList := TObjectList.Create(false); ReadDB; frCommands1.frsgSounds.ObjectRecord := frCommands1.listSounds; frCommands1.frsgSounds.TableName := 'listSounds'; frCommands1.frsgSounds.UpdateGrid; frCommands1.frsgFiles.ObjectRecord := frCommands1.listFiles; frCommands1.frsgFiles.TableName := 'listFiles'; frCommands1.frsgFiles.UpdateGrid; frCommands1.frsgNeiro.ObjectRecord := frCommands1.listNeiro; frCommands1.frsgNeiro.TableName := 'listNeiro'; frCommands1.frsgNeiro.UpdateGrid; for Path in TDirectory.GetFiles(myConst.stlPath) do cbTheme.Items.Add(ExtractFileName(Path)); cbTheme.ItemIndex := strtoint(db.ReadSetting('cbTheme', '-1')); frLog1.FLogList := TList.Create; frPlayerWeb1.Init; userlist := TList.Create; ttw_IRS := TTTW.Create(Self); ttw_IRS.OnMessageRecord := ttwIRCOnMessageRecord; ttw_IRS.OnLog := toLog; ttw_IRS.OnStatus := OnTTWStatus; ttw_API := TTTW_API.Create(Self); end; procedure TTTW_Bot.OnTTWStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); begin Label3.text := AStatusText; end; procedure TTTW_Bot.FormDestroy(Sender: TObject); begin FreeAndNil(frOBS1.ChatBadges); FreeAndNil(frOBS1.ChatEmotes); FreeAndNil(frOBS1.ChatWebServers); FreeAndNil(frOBS1.EventWebServers); FreeAndNil(frOBS1.KandinskyWebServers); FreeAndNil(frOBS1.GameWebServers); FreeAndNil(frRevards1.CustomRewards); frOBS1.BTTV.Free; frOBS1.m7tv.Free; userlist.Free; kePoints.Free; DisconnectProcedure; FreeAndNil(ttw_IRS); FreeAndNil(ttw_ES); FreeAndNil(ttw_API); if Assigned(Kandinsky) then Kandinsky.Free; // frSettings1.Destroy; FreeAndNil(db); FreeAndNil(frAutoActions1.FTimerList); FreeAndNil(frLog1.FLogList); inherited; end; function TTTW_Bot.FindUserRow(const aNick: string): Integer; var I: Integer; begin Result := -1; for I := 0 to userlist.Count - 1 do if userlist[I].login = LowerCase(aNick) then begin Result := I; Break; end; end; function TTTW_Bot.PreprocessMessageText(const aText: string): string; begin Result := LowerCase(aText); Result := StringReplace(Result, #$D, '', [rfReplaceAll]); Result := StringReplace(Result, '󠀀', '', [rfReplaceAll]); Result := Trim(Result) + ' '; end; procedure TTTW_Bot.UpdateWordCounters(const aText: string); var I: Integer; Words: TArray; mode: Boolean; procedure IncrementCounter(ARow: Integer); var Count: Integer; begin Count := frAutoActions1.listCounters[ARow].Count + 1; frAutoActions1.listCounters[ARow].Count := Count; db.SaveRecordArray('listCounters', frAutoActions1.listCounters); frAutoActions1.UpdateGridFromArray; end; function StringToArray(const Input: string): TArray; var Delimiter: char; Words: TArray; I: Integer; begin Delimiter := ','; Words := Input.Split([Delimiter]); SetLength(Result, Length(Words)); for I := 0 to High(Words) do Result[I] := Words[I].Trim; end; function ContainsAnyWord(const str: string; const Words: array of string): Boolean; var I: Integer; begin for I := Low(Words) to High(Words) do begin if Pos(AnsiLowerCase(Words[I]), AnsiLowerCase(str)) > 0 then begin Result := true; exit; end; end; Result := false; end; begin for I := 0 to high(frAutoActions1.listCounters) do begin mode := frAutoActions1.listCounters[I].auto = 1; if mode then begin Words := StringToArray(frAutoActions1.listCounters[I].trigger); if ContainsAnyWord(aText, Words) then IncrementCounter(I); end; end; end; procedure TTTW_Bot.CheckBannedWords(const aText, dname, aID: string); var I: Integer; rx: TRegExpr; begin rx := TRegExpr.Create; try for I := 0 to high(frAutoActions1.listBanWords) do begin rx.Expression := frAutoActions1.listBanWords[I].regexp; if rx.Exec(aText) then begin if aID = '0' then ttw_API.banUserTime(dname, 86400) else ttw_API.banUserTime(aID, 86400); Break; end; end; finally rx.Free; end; end; procedure TTTW_Bot.ttwIRCOnMessageRecord(aRecord: TTwitchChatMessage); // обработка нового сообщения var processedText: string; firstWord: string; commandText: string; responseText, s: string; u: TUser; I: Integer; function ExtractFirstWord(var aText: string): string; var posSpace: Integer; begin posSpace := Pos(' ', aText); if posSpace > 0 then begin Result := Copy(aText, 1, posSpace - 1); Result := StringReplace(Result, ',', '', [rfReplaceAll]); Result := StringReplace(Result, '.', '', [rfReplaceAll]); Result := StringReplace(Result, '?', '', [rfReplaceAll]); end else Result := aText; end; function IsCommand(const aWord: string): Boolean; begin Result := (Length(aWord) > 0) and (aWord[1] = '!'); end; function ExtractCommandText(const aText, aFirstWord: string): string; begin Result := Copy(aText, Length(aFirstWord) + 1, Length(aText)); end; function ProcessCommand(const aCommand: string): string; var I: Integer; begin Result := ''; for I := 0 to high(frCommands1.listCommands) do begin if ContainsText(aCommand, frCommands1.listCommands[I].R1) then begin Result := frCommands1.listCommands[I].R2; Break; end; end; end; begin if aRecord.Username = '' then exit; I := FindUserRow(aRecord.Username); if I <> -1 then begin // user есть u := userlist[I]; end else begin u.ID := aRecord.UserId; u.login := aRecord.Username; u.isVIP := false; u.isModer := false; u.isO := false; u.created_at := StrToDate('01.01.1990'); u.follow_at := StrToDate('01.01.1990'); u.isO_today := false; userlist.Add(u); end; I := FindUserRow(aRecord.Username); if (u.isO) and (NOT u.isO_today) then begin ttw_API.shoutouts(u.ID); u.isO_today := true; end; userlist[I] := u; processedText := PreprocessMessageText(aRecord.Message); UpdateWordCounters(processedText); CheckBannedWords(processedText, aRecord.DisplayName, aRecord.UserId); if (frCommands1.frContruct1.cbTextToSpeach.IsChecked) and (processedText[1] = '!') and (processedText[2] = '!') and (processedText[3] = '!') then begin s := StringReplace(processedText, '!!!', '', [rfReplaceAll]); s := Trim(s); toSpeech(s); exit; end; if (frCommands1.frContruct1.cbHelloTTS.IsChecked) and (aRecord.FirstMsg = 1) then toSpeech('приветствую, ' + IfThen(aRecord.DisplayName <> '', aRecord.DisplayName, aRecord.Username)); firstWord := ExtractFirstWord(processedText); if IsCommand(firstWord) then begin commandText := ExtractCommandText(processedText, firstWord); responseText := ProcessCommand(firstWord); decodeResponseSendToTTW(responseText, commandText, aRecord.DisplayName); end; TThread.Queue(nil, procedure begin frOBS1.MsgToWebServer(aRecord); end); PlayNotifySound((aRecord.Moder = 1), (aRecord.Vip = 1), (aRecord.Subscriber = 1)); processedText := StringReplace(processedText, '!', '', [rfReplaceAll]); checkAndSendToGames(aRecord.DisplayName, processedText); end; procedure TTTW_Bot.PlayNotifySound(aMod, aVip, aSub: Boolean); var s: string; sm: TSongMachine; begin sm := TSongMachine.Create; try if (aMod) and (frNotify1.chEnNotifyMod.IsChecked) and (frNotify1.edtNotifyFileNameMod.text <> '') then begin if frNotify1.cbNotifyFileAgain1.IsChecked then s := frNotify1.edtNotifyFileName.text else s := frNotify1.edtNotifyFileNameMod.text; sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeMod.Value))); exit; end; if (aVip) and (frNotify1.chEnNotifyVip.IsChecked) and (frNotify1.edtNotifyFileNameVip.text <> '') then begin if frNotify1.cbNotifyFileAgain2.IsChecked then s := frNotify1.edtNotifyFileName.text else s := frNotify1.edtNotifyFileNameVip.text; sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeVip.Value))); exit; end; if (aSub) and (frNotify1.chEnNotifySub.IsChecked) and (frNotify1.edtNotifyFileNameSub.text <> '') then begin if frNotify1.cbNotifyFileAgain3.IsChecked then s := frNotify1.edtNotifyFileName.text else s := frNotify1.edtNotifyFileNameSub.text; sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeSub.Value))); exit; end; if (frNotify1.chEnNotify.IsChecked) and (frNotify1.edtNotifyFileName.text <> '') then begin s := frNotify1.edtNotifyFileName.text; sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolume.Value))); exit; end; finally sm.Free; end; end; procedure TTTW_Bot.frAutoActions1btnCounterAddClick(Sender: TObject); var I: Integer; begin frAutoActions1.btnCounterAddClick(Sender); frCommands1.frContruct1.cbCounterName.Items.Clear; for I := 0 to high(frAutoActions1.listCounters) do frCommands1.frContruct1.cbCounterName.Items.Add (frAutoActions1.listCounters[I].counterName); end; procedure TTTW_Bot.frAutoActions1btnCounterDeleteClick(Sender: TObject); var I: Integer; begin frAutoActions1.btnCounterDeleteClick(Sender); frCommands1.frContruct1.cbCounterName.Items.Clear; for I := 0 to high(frAutoActions1.listCounters) do frCommands1.frContruct1.cbCounterName.Items.Add (frAutoActions1.listCounters[I].counterName); end; procedure TTTW_Bot.frAutoActions1btnCounterEditClick(Sender: TObject); var I: Integer; begin frAutoActions1.btnCounterEditClick(Sender); frCommands1.frContruct1.cbCounterName.Items.Clear; for I := 0 to high(frAutoActions1.listCounters) do frCommands1.frContruct1.cbCounterName.Items.Add (frAutoActions1.listCounters[I].counterName); end; procedure TTTW_Bot.frCommands1btnRandAddClick(Sender: TObject); begin frCommands1.btnRandAddClick(Sender); end; procedure TTTW_Bot.frOBS1btnDeleteeChatClick(Sender: TObject); begin frOBS1.btnDeleteeChatClick(Sender); end; procedure TTTW_Bot.frSettings1btnDAStartClick(Sender: TObject); begin frSettings1.btnDAStartClick(Sender); end; procedure TTTW_Bot.frTTS1btnSendClick(Sender: TObject); begin frTTS1.btnSendClick(Sender); end; procedure TTTW_Bot.ReadDB; function XorDecryptToStrings(const InputFile, Key: string): TStrings; var InStream: TFileStream; MemStream: TMemoryStream; KeyBytes: TBytes; KeyLen, KeyIndex: Integer; B: Byte; begin KeyBytes := TEncoding.ANSI.GetBytes(Key); KeyLen := Length(KeyBytes); if KeyLen = 0 then raise Exception.Create('Ключ не может быть пустым'); InStream := TFileStream.Create(InputFile, fmOpenRead); try MemStream := TMemoryStream.Create; try KeyIndex := 0; while InStream.Position < InStream.Size do begin InStream.ReadBuffer(B, 1); B := B xor KeyBytes[KeyIndex]; MemStream.WriteBuffer(B, 1); KeyIndex := (KeyIndex + 1) mod KeyLen; end; MemStream.Position := 0; Result := TStringList.Create; try Result.LoadFromStream(MemStream, TEncoding.ANSI); except Result.Free; // Освобождаем при ошибке загрузки raise; end; finally MemStream.Free; end; finally InStream.Free; end; end; // Загрузка компонентов настроек (TEdit, TCheckBox) procedure LoadSettingsComponents; var I: Integer; c: TComponent; begin for I := 0 to frSettings1.ComponentCount - 1 do begin 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 := frSettings1.edtChannel.text; end; // Загрузка данных в гриды команд procedure LoadGridsData; begin db.LoadRecordArray('RandomCounters', frCommands1.RandomCounters); db.LoadRecordArray('listSounds', frCommands1.listSounds); db.LoadRecordArray('listFiles', frCommands1.listFiles); db.LoadRecordArray('listNeiro', frCommands1.listNeiro); db.LoadRecordArray('listCommands', frCommands1.listCommands); frCommands1.UpdateGridFromArray; end; // Загрузка списка групп procedure LoadGroupNames; begin db.getGroupName(frCommands1.frGroupsRequest1.lbRandomGroup.Items); end; // Загрузка зашифрованного конфига procedure LoadEncryptedConfig; var tempList: TStrings; // Временный список для результата I: Integer; begin if not FileExists(myConst.cfg1) then exit; tempList := nil; // Инициализация try tempList := XorDecryptToStrings(myConst.cfg1, 'fgvasrgEFAXFAFAS'); for I := 0 to tempList.Count - 1 do begin var eqPos := Pos('=', tempList[I]); if eqPos > 0 then begin var Key := Trim(Copy(tempList[I], 1, eqPos - 1)); var Value := Trim(Copy(tempList[I], eqPos + 1, MaxInt)); if Key = 'k1' then appconst.TTV_ClientID := Value else if Key = 'k2' then appconst.AI_GigaChat_AC := Value else if Key = 'k3' then appconst.AI_GigaChat_ClientID := Value else if Key = 'k4' then appconst.AI_ChatGPT_Token := Value else if Key = 'k5' then appconst.AI_DeepSeec_Token := Value else if Key = 'k6' then appconst.DA_ClientID := Value else if Key = 'k7' then appconst.DA_Sicret := Value else if Key = 'k8' then appconst.DA_URL := Value; end; end; 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 <> ''); frSettings1.btnGetDADef.Visible := (appconst.DA_ClientID <> '') and (appconst.DA_Sicret <> '') and (appconst.DA_URL <> ''); finally tempList.Free; // Важно: освобождаем временный список! end; end; // Загрузка настроек уведомлений procedure LoadNotifySettings; var I: Integer; c: TComponent; begin for I := 0 to frNotify1.ComponentCount - 1 do begin c := frNotify1.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') else if c is TSwitch then TSwitch(c).IsChecked := (db.ReadSetting(TSwitch(c).Name) = 'True') else if c is TTrackBar then TTrackBar(c).Value := strtoint(db.ReadSetting(TTrackBar(c).Name, '100')); end; end; // Загрузка настроек ИИ procedure LoadAISettings; var I: Integer; c: TComponent; ii: Integer; // Настройки GigaChat procedure SetupGigaChatSettings; begin frAI1.rbGC.IsChecked := true; frAI1.Label45.text := 'ClientID'; frAI1.Label47.text := 'Autorization Code'; frAI1.Label1.Visible := false; frAI1.edtAIP2.Visible := true; frAI1.edtAIP2.Password := true; frAI1.edtAIP3.Visible := false; frAI1.cbOllama.Visible := false; end; // Настройки DeepSeek procedure SetupDeepSeekSettings; begin frAI1.rbDS.IsChecked := true; frAI1.Label45.text := 'API Token'; frAI1.Label47.text := ''; frAI1.Label1.Visible := false; frAI1.edtAIP2.Visible := false; frAI1.edtAIP3.Visible := false; frAI1.cbOllama.Visible := false; end; // Настройки ChatGPT procedure SetupChatGPTSettings; begin frAI1.rbCG.IsChecked := true; frAI1.Label45.text := 'API Token'; frAI1.Label47.text := ''; frAI1.Label1.Visible := false; frAI1.edtAIP2.Visible := false; frAI1.edtAIP3.Visible := false; frAI1.cbOllama.Visible := false; end; // Настройки кастомного ИИ procedure SetupCustomAISettings; begin frAI1.RBCustom.IsChecked := true; frAI1.Label45.text := 'API Token'; frAI1.Label47.text := 'URL'; frAI1.Label1.Visible := true; frAI1.edtAIP2.Visible := true; frAI1.edtAIP2.Password := false; frAI1.edtAIP3.Visible := true; frAI1.cbOllama.Visible := true; frAI1.cbOllama.IsChecked := db.ReadSetting(frAI1.cbOllama.Name) = '1'; end; begin for I := 0 to frAI1.ComponentCount - 1 do begin c := frAI1.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) = '1'; end; ii := strtoint(db.ReadSetting('aiIndex', '0')); case ii of 0: SetupGigaChatSettings; 1: SetupDeepSeekSettings; 2: SetupChatGPTSettings; 3: SetupCustomAISettings; end; frSettings1.Init; end; // Загрузка гридов автоматических действий procedure LoadAutoActionsGrids; var I: Integer; begin db.LoadRecordArray('listTimer', frAutoActions1.listTimer); db.LoadRecordArray('listBanWords', frAutoActions1.listBanWords); db.LoadRecordArray('listCounters', frAutoActions1.listCounters); for I := 0 to High(frAutoActions1.listCounters) do frCommands1.frContruct1.cbCounterName.Items.Add (frAutoActions1.listCounters[I].counterName); frCommands1.frContruct1.cbTextToSpeach.IsChecked := db.ReadSetting('cbTextToSpeach', 'False') = 'True'; frCommands1.frContruct1.cbHelloTTS.IsChecked := db.ReadSetting('cbHelloTTS', 'False') = 'True'; frAutoActions1.initTimers; frAutoActions1.UpdateGridFromArray; end; // Загрузка интеграций с ОБС procedure LoadOBSGrids; var I: Integer; begin db.LoadRecordArray('listChats', frOBS1.listChats); frOBS1.BTTV := TBTTV.Create; frOBS1.m7tv := t7tv.Create; frOBS1.ChatBadges := TList.Create; frOBS1.ChatEmotes := TList.Create; frOBS1.ChatWebServers := TList.Create; for I := 0 to High(frOBS1.listChats) do begin frOBS1.CreateWebChat(frOBS1.listChats[I]); end; db.LoadRecordArray('listNotify', frOBS1.listNotify); frOBS1.EventWebServers := TList.Create; for I := 0 to High(frOBS1.listNotify) do begin frOBS1.CreateWebEvents(frOBS1.listNotify[I]); end; db.LoadRecordArray('listKandinsky', frOBS1.listKandinsky); frOBS1.KandinskyWebServers := TList.Create; if (frAI1.edtKandiKey.text <> '') and (frAI1.edtKandiSecret.text <> '') then begin for I := 0 to High(frOBS1.listKandinsky) do begin frOBS1.CreateWebKandinsky(frOBS1.listKandinsky[I]); end; end; db.LoadRecordArray('listGames', frOBS1.listGames); frOBS1.GameWebServers := TList.Create; for I := 0 to High(frOBS1.listGames) do begin frOBS1.CreateWebGame(frOBS1.listGames[I].game, frOBS1.listGames[I].port); end; frOBS1.UpdateGridFromArray; end; // Загрузка навыков procedure LoadSkills; begin db.LoadRecordArray('ListEvents', frEvents1.ListEvents); frEvents1.UpdateGrid; frRevards1.CustomRewards := TList.Create; frEvents1.CustomRewardEvents := TList.Create; kePoints := TKE.Create(frEvents1.edtParams, frEvents1.cbKey1, frEvents1.cbKey2, frEvents1.cbKey3); frTTS1.btnUpdateVoicesClick(Self); frTTS1.cbVoices.ItemIndex := strtoint(db.ReadSetting('cbVoices', '0')); frTTS1.cbOutput.ItemIndex := strtoint(db.ReadSetting('cbOutput', '0')); end; begin LoadAISettings; LoadSkills; LoadSettingsComponents; LoadGridsData; LoadGroupNames; LoadEncryptedConfig; LoadNotifySettings; LoadOBSGrids; LoadAutoActionsGrids; end; procedure TTTW_Bot.SpeedButton1Click(Sender: TObject); begin ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/incadence'), nil, nil, 1); end; 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 TTTW_Bot.SpeedButton3Click(Sender: TObject); begin // https://www.flaticon.com/ru/authors/karacis ShellExecute(0, 'open', pwidechar('https://www.flaticon.com/ru/authors/karacis'), nil, nil, 1); end; procedure TTTW_Bot.toLog(aModule, aMethod, aMessage: string; aCode: Integer); begin TThread.Synchronize(nil, procedure var ml: TRLog; begin // Инициализация всех полей записи ml.rTime := Now; case aCode of 0: ml.rType := 'INFO'; 1: ml.rType := 'WARNING'; 2: ml.rType := 'ERROR'; 3: ml.rType := 'DEBUG'; else ml.rType := 'UNKNOWN'; end; ml.rModule := aModule; // string ml.rMethod := aMethod; // string ml.rMessage := aMessage; // string // Добавляем запись в список frLog1.FLogList.Add(ml); // Обновляем грид frLog1.UpdateGridFilters; end); end; procedure TTTW_Bot.decodeResponseSendToTTW(aResponseText, aCommandText, adName: string); var res: string; ID: string; RowIndex: Integer; begin res := ''; res := aResponseText; RowIndex := FindUserRow(adName); ID := userlist[RowIndex].ID; if ID = '' then ID := '0'; // ----------------------------------------Группы ответов res := ResponsParserGroups(res); // ----------------------------------------рандомы res := ResponsParserRandoms(res); // ----------------------------------------счетчики res := ResponsParserCounters(res); // ----------------------------------------константы res := ResponsParserStatic(res, adName, aCommandText); // ----------------------------------------апи команды res := ResponsParserAPI(res, adName); // ----------------------------------------GPT res := ResponsParserGPT(res, aCommandText); // ----------------------------------------звуки res := ResponsParserSounds(res); // ----------------------------------------text res := ResponsParserText(res); // ----------------------------------------AIGen res := ResponsParserAIGen(res, aCommandText); // ----------------------------------------AIPic res := ResponsParserAIPic(res, aCommandText, adName); if res <> '' then ttw_IRS.sendMessage(res); end; function TTTW_Bot.Pars(T_, text, _T: string): string; var A, B: Integer; begin Result := ''; A := Pos(T_, text); if A = 0 then exit; A := A + Length(T_); B := Pos(_T, text, A); if B > 0 then Result := Copy(text, A, B - A); end; function TTTW_Bot.ResponsParserGroups(inMess: string): string; var ss, ss2, res: string; sl: TStringList; begin res := inMess; while Pos('{{', res) <> 0 do begin ss := Pars('{{', res, '}}'); sl := TStringList.Create; randomize; db.getGroupResponse(ss, sl); ss2 := sl[random(sl.Count)]; res := StringReplace(res, '{{' + ss + '}}', ss2, [rfReplaceAll]); end; Result := res; end; function TTTW_Bot.ResponsParserSounds(inMess: string): string; var ss, res: string; I, p: Integer; sm: TSongMachine; begin res := inMess; sm := TSongMachine.Create; try if ContainsText(res, '||') then begin // Находим позицию первого '||' p := Pos('||', res); // Копируем все, что идет после первого '||' ss := Copy(res, p + 2, Length(res) - p - 1); // +2 чтобы пропустить первый '||' // Находим позицию второго '||' p := Pos('||', ss); if p > 0 then begin // Копируем текст между '||' ss := Copy(ss, 1, p - 1); if ss <> '' then begin // Ищем значение в sgSAFiles for I := 0 to high(frCommands1.listSounds) do begin if frCommands1.listSounds[I].R1 = ss then begin if FileExists(frCommands1.listSounds[I].R2) then begin { PlaySong(sgSAFiles.Cells[1, i], inttostr(round(tbSoundVolume.Value)), True); } sm.PlayPublic(frCommands1.listSounds[I].R2, inttostr(round(100))); end else begin toLog('uGeneral', 'ResponsParserSounds', 'Файл "' + frCommands1.listSounds[I].R1 + '" не найден: ' + frCommands1.listSounds[I].R2, 2); end; Break; end; end; // Удаляем обработанный фрагмент из исходной строки res := StringReplace(res, '||' + ss + '||', '', [rfReplaceAll]); end; end; end; finally sm.Free; end; Result := res; end; function TTTW_Bot.ResponsParserText(inMess: string): string; var ss, res, fn, rres: string; sll: TStringList; I, p: Integer; begin res := inMess; if ContainsText(res, '|(') then begin p := Pos('|(', res); ss := Copy(res, p, Length(res) - p); p := Pos('|(', ss, 2); ss := Copy(ss, 1, p + 1); ss := StringReplace(ss, '|(', '', [rfReplaceAll]); if ss <> '' then begin for I := 0 to high(frCommands1.listFiles) do begin if frCommands1.listFiles[I].R1 = ss then begin fn := frCommands1.listFiles[I].R2; Break; end; end; sll := TStringList.Create; try sll.LoadFromFile(fn, TEncoding.UTF8); rres := sll.text; finally sll.Free; end; if rres <> '' then begin if Length(rres) > 450 then begin rres := Copy(rres, 1, 450); rres := rres + '...'; end; rres := StringReplace(rres, #13#10, ' ', [rfReplaceAll]); res := StringReplace(res, '|(' + ss + '|(', rres, [rfReplaceAll]); end; end; end; Result := res; end; function TTTW_Bot.ResponsParserRandoms(inMess: string): string; var r, res: string; I: Integer; function RandomInRange(MinValue, MaxValue: Integer): Integer; begin randomize; Result := random(MaxValue - MinValue + 1) + MinValue; end; begin res := inMess; while ContainsText(res, '[[') do begin r := Pars('[[', res, ']]'); for I := 0 to high(frCommands1.RandomCounters) do begin if frCommands1.RandomCounters[I].rndName = r then begin res := StringReplace(res, '[[' + r + ']]', inttostr(RandomInRange(frCommands1.RandomCounters[I].Ot, frCommands1.RandomCounters[I].ToValue)), [rfReplaceAll]); Break; end; end; end; Result := res; end; function TTTW_Bot.ResponsParserCounters(inMess: string): string; var r, res: string; I: Integer; begin res := inMess; while ContainsText(res, '``') do begin r := Pars('``', res, '``'); for I := 0 to high(frAutoActions1.listCounters) do begin if frAutoActions1.listCounters[I].counterName = r then begin res := StringReplace(res, '``' + r + '``', inttostr(frAutoActions1.listCounters[I].Count), [rfReplaceAll]); Break; end; end; end; Result := res; end; function TTTW_Bot.ResponsParserStatic(inMess, adName, aCommandText: string): string; var res, RandomUserName: string; begin res := inMess; res := StringReplace(res, '[USERNAME]', '@' + adName, [rfReplaceAll]); res := StringReplace(res, '[TO]', aCommandText, [rfReplaceAll]); if ContainsText(res, '[RANDOMUSER]') then begin if Pos('@', aCommandText) <> 0 then begin RandomUserName := aCommandText; end else begin randomize; RandomUserName := '@' + userlist[random(userlist.Count)].login; end; res := StringReplace(res, '[RANDOMUSER]', RandomUserName, [rfReplaceAll]); end; Result := res; end; procedure TTTW_Bot.gptError(Msg: string); begin toLog('GPT', 'gptError', Msg, 2); end; function TTTW_Bot.ResponsParserAIGen(inMess, aCommandText: string): string; var res, GPTRequest: string; GigaChat: TGigaChat; ChatAPI: TChatAPI; mystr: string; p, I: Integer; ss: string; begin res := inMess; if ContainsText(res, '<|') then begin // Находим позицию первого '||' p := Pos('<|', res); // Копируем все, что идет после первого '<|' ss := Copy(res, p + 2, Length(res) - p - 1); // +2 чтобы пропустить первый '<|' // Находим позицию второго '<|' p := Pos('<|', ss); if p > 0 then begin // Копируем текст между '<|' ss := Copy(ss, 1, p - 1); if ss <> '' then begin // Ищем значение в sgSAFiles for I := 0 to high(frCommands1.listNeiro) do begin if frCommands1.listNeiro[I].R1 = ss then begin mystr := frCommands1.listNeiro[I].R2; Break; end; end; // Удаляем обработанный фрагмент из исходной строки // res := StringReplace(res, '<|' + ss + '<|', mystr, [rfReplaceAll]); end; end; end; mystr := StringReplace(mystr, '[UT]', aCommandText, [rfReplaceAll]); mystr := StringReplace(mystr, '[AI]', '', [rfReplaceAll]); if (frAI1.edtAIP1.text = '') then begin GPTRequest := 'тут должен быть ответ нейросети, но стример зажал логиниться'; end else begin if frAI1.rbGC.IsChecked then // GigaChat begin GigaChat := TGigaChat.Create(Self, frAI1.edtAIP1.text, frAI1.edtAIP2.text, frAI1.edtGPTPrefix.text); try try GPTRequest := GigaChat.GetGPTRequest ('https://gigachat.devices.sberbank.ru/api/v1/chat/completions', 'GigaChat', mystr); except on E: Exception do toLog('uGeneral', 'ResponsParserAIGen.gigachat', E.Message, 2); end; finally GigaChat.Destroy; end; end; if frAI1.rbDS.IsChecked then // DeepSeek begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try ChatAPI.OnError := gptError; try GPTRequest := ChatAPI.GetGPTRequest ('https://api.deepseek.com/chat/completions', 'deepseek-chat', mystr); except on E: Exception do toLog('uGeneral', 'ResponsParserAIGen.deepseek', E.Message, 2); end; finally ChatAPI.Destroy; end; end; if frAI1.rbCG.IsChecked then // ChatGPT begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try try GPTRequest := ChatAPI.GetGPTRequest ('https://api.openai.com/v1/chat/completions', 'gpt-3.5-turbo', mystr); except on E: Exception do toLog('uGeneral', 'ResponsParserAIGen.openai', E.Message, 2); end; finally ChatAPI.Destroy; end; end; if frAI1.RBCustom.IsChecked then // Custom begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try try GPTRequest := ChatAPI.GetGPTRequest(frAI1.edtAIP2.text, frAI1.edtAIP3.text, mystr); except on E: Exception do toLog('uGeneral', 'ResponsParserAIGen.custom', E.Message, 2); end; finally ChatAPI.Destroy; end; end; GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); if GPTRequest = '' then GPTRequest := 'ой, кажется нейронка пока не доступна'; end; res := StringReplace(res, '<|' + ss + '<|', GPTRequest, [rfReplaceAll]); Result := res; end; function TTTW_Bot.ResponsParserAIPic(inMess, aCommandText, aNick: string): string; var res: string; I: Integer; begin res := inMess; if ContainsText(res, '[Kandinsky]') then begin for I := 0 to frOBS1.KandinskyWebServers.Count - 1 do frOBS1.KandinskyWebServers[I].WebServerKandinsky.generate (aCommandText, aNick); res := StringReplace(res, '[Kandinsky]', '', [rfReplaceAll]); end; Result := res; end; function TTTW_Bot.ResponsParserAPI(inMess, adName: string): string; var res, follow, age, ID: string; RowIndex: Integer; u: TUser; function GetPeriodEnding(n, r: Integer): string; var res: array [0 .. 3, 0 .. 2] of string; begin res[0, 0] := 'год'; res[0, 1] := 'года'; res[0, 2] := 'лет'; res[1, 0] := 'месяц'; res[1, 1] := 'месяца'; res[1, 2] := 'месяцев'; res[2, 0] := 'день'; res[2, 1] := 'дня'; res[2, 2] := 'дней'; res[3, 0] := 'раз'; res[3, 1] := 'раза'; res[3, 2] := 'раз'; if (n mod 10 = 1) and (n mod 100 <> 11) then Result := res[r, 0] else if (n mod 10 >= 2) and (n mod 10 <= 4) and ((n mod 100 < 10) or (n mod 100 >= 20)) then Result := res[r, 1] else Result := res[r, 2]; end; function GetDateDifference(const inputDate: string): string; var currentDate, targetDate: TDateTime; years, months, days: Integer; begin try targetDate := StrToDate(inputDate); currentDate := Now; years := YearsBetween(currentDate, targetDate); targetDate := IncYear(targetDate, years); months := MonthsBetween(currentDate, targetDate); targetDate := IncMonth(targetDate, months); days := DaysBetween(currentDate, targetDate); Result := inttostr(years) + ' ' + GetPeriodEnding(years, 0) + ' ' + inttostr(months) + ' ' + GetPeriodEnding(months, 1) + ' ' + inttostr(days) + ' ' + GetPeriodEnding(days, 2); except on E: Exception do begin toLog('uGeneral', 'ResponsParserAPI.GetDateDifference', E.Message, 2); Result := ''; end; end; end; begin res := inMess; RowIndex := FindUserRow(adName); u := userlist[RowIndex]; ID := u.ID; if ContainsText(res, '[FOLLOW]') then begin if YearOf(u.follow_at) < 2000 then u.follow_at := ttw_API.getFollow(ID); follow := GetDateDifference(DateToStr(u.follow_at)); res := StringReplace(res, '[FOLLOW]', follow, [rfReplaceAll]); end; if ContainsText(res, '[AGE]') then begin if YearOf(userlist[RowIndex].created_at) < 2000 then u := ttw_API.getUserbyLogin(u.login); age := GetDateDifference(DateToStr(u.created_at)); res := StringReplace(res, '[AGE]', age, [rfReplaceAll]); end; userlist[RowIndex] := u; if ContainsText(res, '[STAT]') then begin var avg_viewers: Integer; var max_viewers: Integer; var hours_watched: Integer; var followers: Integer; var followers_total: Integer; ttw_API.getTTWStat(db.FChannel, avg_viewers, max_viewers, hours_watched, followers, followers_total); var resultStat: string; resultStat := 'Статистика канала за месяц: Средний онлайн: ' + inttostr(avg_viewers) + '; Максимальный онлайн: ' + inttostr(max_viewers) + '; Часов просмотра: ' + inttostr(hours_watched) + '; Подписчиков за месяц: ' + inttostr(followers) + '; Всего подписчиков: ' + inttostr(followers_total); res := StringReplace(res, '[STAT]', resultStat, [rfReplaceAll]); end; Result := res; end; function TTTW_Bot.ResponsParserGPT(inMess, aCommandText: string): string; var res, GPTRequest: string; GigaChat: TGigaChat; ChatAPI: TChatAPI; begin res := inMess; if ContainsText(res, '[AI]') then begin begin if frAI1.rbGC.IsChecked then // GigaChat begin GigaChat := TGigaChat.Create(Self, frAI1.edtAIP1.text, frAI1.edtAIP2.text, frAI1.edtGPTPrefix.text); try try GPTRequest := GigaChat.GetGPTRequest ('https://gigachat.devices.sberbank.ru/api/v1/chat/completions', 'GigaChat', aCommandText); except on E: Exception do toLog('uGeneral', 'ResponsParserGPT.gigachat', E.Message, 2); end; finally GigaChat.Destroy; end; end; if frAI1.rbDS.IsChecked then // DeepSeek begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try ChatAPI.OnError := gptError; try GPTRequest := ChatAPI.GetGPTRequest ('https://api.deepseek.com/chat/completions', 'deepseek-chat', aCommandText); except on E: Exception do toLog('uGeneral', 'ResponsParserGPT.deepseek', E.Message, 2); end; finally ChatAPI.Destroy; end; end; if frAI1.rbCG.IsChecked then // ChatGPT begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try try GPTRequest := ChatAPI.GetGPTRequest ('https://api.openai.com/v1/chat/completions', 'gpt-3.5-turbo', aCommandText); except on E: Exception do toLog('uGeneral', 'ResponsParserGPT.openai', E.Message, 2); end; finally ChatAPI.Destroy; end; end; if frAI1.RBCustom.IsChecked then // Custom begin ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, frAI1.edtGPTPrefix.text); try try GPTRequest := ChatAPI.GetGPTRequest(frAI1.edtAIP2.text, frAI1.edtAIP3.text, aCommandText, frAI1.cbOllama.IsChecked); except on E: Exception do toLog('uGeneral', 'ResponsParserGPT.custom', E.Message, 2); end; finally ChatAPI.Destroy; end; end; GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); if GPTRequest = '' then GPTRequest := 'ой, кажется нейронка пока не доступна'; end; res := StringReplace(res, '[AI]', GPTRequest, [rfReplaceAll]); end; Result := res; end; end.