unit fOBS;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, uCustomEmoties,
  System.Variants, uWebServerChat, uWebServerEvents, fColorSettings,
  fFontSettings,
  System.Generics.Collections, uWebServerKandinsky,
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  System.Rtti, FMX.Grid.Style, FMX.Grid, FMX.ScrollBox, FMX.Edit, FMX.Colors,
  FMX.ListBox, FMX.EditBox, FMX.SpinBox, FMX.Controls.Presentation, uRecords,
  System.Generics.Defaults, System.Character;

type
  TChatWebServers = record
    port: integer;
    MaxMsg: integer;
    TimeMsg: integer;
    Freez: boolean;
    StyleBorderColor: string;
    StyleBlockColor: string;
    StyleBlockBorderSize: integer;
    StyleBlockPadding: integer;
    FontStyleDefault: string;
    FontColor: string;
    BColor: string;
    FontSize: integer;
    WebServerChat: TTTW_Chat;
  end;

type
  TEventWebServers = record
    port: integer;
    typeEvent: integer;
    se: TStyleEvent;
    WebServerChat: TTTW_Events;
  end;

type
  TKandinskyWebServers = record
    port: integer;
    WebServerKandinsky: TKandinsky_Web;
  end;

type
  TfrOBS = class(TFrame)
    sgWebChats: TStringGrid;
    btnCreateOBSChat: TButton;
    btnDeleteeChat: TButton;
    Label1: TLabel;
    IntegerColumn1: TIntegerColumn;
    StringColumn1: TStringColumn;
    StringColumn2: TStringColumn;
    btnCreateOBSNotify: TButton;
    btnCreateOBSKandinsky: TButton;
    procedure btnDeleteeChatClick(Sender: TObject);
    procedure btnCreateOBSKandinskyClick(Sender: TObject);
    procedure btnCreateOBSChatClick(Sender: TObject);
    procedure btnCreateOBSNotifyClick(Sender: TObject);
    procedure sgWebChatsCellDblClick(const Column: TColumn; const Row: integer);
  private
    { Private declarations }

    function checkBttv(aMsg: string): string;
    function ReplaceEmotesInMessage(const MessageText,
      EmotesString: string): string;
    function FindEmoteByID(const ID: string): TEmotes;
    function GetBadgesHTML(Badges: string): string;
  public
    { Public declarations }
    listChats: TArray<TOBSChat>;
    listNotify: TArray<TOBSNotify>;
    listKandinsky: TArray<TOBSKandinsky>;

    BTTV: TBTTV;
    m7tv: t7tv;
    ChatBadges: Tlist<TChatBadge>;
    ChatEmotes: Tlist<TEmotes>;
    ChatWebServers: Tlist<TChatWebServers>;
    EventWebServers: Tlist<TEventWebServers>;
    KandinskyWebServers: Tlist<TKandinskyWebServers>;
    procedure MsgToWebServer(const aRecord: TTwitchChatMessage);
    procedure toEventWebServer(aEvent: TFollowEvent); overload; // follow
    procedure toEventWebServer(aEvent: TSubEvent); overload; // sub
    procedure toEventWebServer(aEvent: TRaidEvent); overload; // raid
    procedure toEventWebServer(aEvent: TGiftEvent); overload; // gift
    procedure toEventWebServer(aNick, aSum, aText: string); overload; // donate
    procedure CreateWebEvents(eventsSettings: TOBSNotify);

    procedure CreateWebChat(chatSettings: TOBSChat);
    procedure UpdateGridFromArray;
    procedure AddChat(newRecord: TOBSChat);
    procedure EdtChat(newRecord: TOBSChat; oldPort: integer);
    procedure DelChat(aPort: integer);

    procedure AddNotify(newRecord: TOBSNotify);
    procedure EdtNotify(newRecord: TOBSNotify; oldPort: integer);
    procedure DelNotify(aPort: integer);

    procedure AddKandinsky(newRecord: TOBSKandinsky);
    procedure DelKandinsky(aPort: integer);
    procedure CreateWebKandinsky(aRecord: TOBSKandinsky);
  end;

implementation

{$R *.fmx}

uses uGeneral, uCreateChat, uCreateNotify;

{ TfrOBS }

function TfrOBS.checkBttv(aMsg: string): string;
var
  Words: tstringlist;
  i: integer;
  CurrentWord, Url: string;
begin
  Words := tstringlist.Create;
  try
    // Разбиваем строку на слова по пробелам
    Words.Delimiter := ' ';
    Words.StrictDelimiter := True; // Игнорировать повторяющиеся пробелы
    Words.DelimitedText := aMsg;

    // Обработка слов
    for i := 0 to Words.Count - 1 do
    begin
      CurrentWord := Words[i];
      Url := BTTV.generateURL(CurrentWord);
      if Url = '' then
        Url := m7tv.generateURL(CurrentWord);
      if Url <> '' then
        Words[i] := Format('<img src="%s" width="18" height="18">', [Url]);
    end;

    // Собираем результат
    result := Words.text;
  finally
    Words.Free;
  end;
end;

function TfrOBS.GetBadgesHTML(Badges: string): string;
var
  BadgeList: TArray<string>;
  CodeParts: TArray<string>;
  CurrentCode, SetId, VersionId: string;
  Badge: TChatBadge;
  Version: TBadgeVersion;
  Found: boolean;
begin
  // Разбиваем строку на отдельные бейдж-коды
  BadgeList := Badges.Split([',']);

  for CurrentCode in BadgeList do
  begin
    // Разделяем SetId и VersionId
    CodeParts := CurrentCode.Split(['/']);
    if Length(CodeParts) <> 2 then
      Continue;

    SetId := CodeParts[0];
    VersionId := CodeParts[1];
    Found := false;

    // Ищем соответствующий бейдж
    for Badge in ChatBadges do
    begin
      if Badge.SetId = SetId then
      begin
        // Ищем нужную версию
        for Version in Badge.Versions do
        begin
          if Version.ID = VersionId then
          begin
            // Формируем HTML-тег
            result := result +
              Format(' <img src="%s" width=18 height=18 alt="%s" title="%s">',
              [Version.ImageUrl1x, Version.Title, Version.Description]);
            Found := True;
            Break;
          end;
        end;
        if Found then
          Break;
      end;
    end;

    // Если не нашли - добавляем заглушку
    if not Found then
      result := result + ' <img src="placeholder.png" width=18 height=18>';
  end;
end;

procedure TfrOBS.MsgToWebServer(const aRecord: TTwitchChatMessage);
var
  s: string;
  ms: TStyleChat;
  i: integer;
begin
  s := checkBttv(aRecord.Message);
  if aRecord.Emotes <> '' then
    s := ReplaceEmotesInMessage(s, aRecord.Emotes);

  ms.Nick := GetBadgesHTML(aRecord.Badges) + '<span class="nick" style="color:'
    + aRecord.Color + '">' + aRecord.DisplayName + '</span>';
  ms.Context := '<span class="text">' + s + '</span>';

  for i := 0 to ChatWebServers.Count - 1 do
  begin
    ms.FontColor := ChatWebServers[i].FontColor;
    ms.FontSize := ChatWebServers[i].FontSize;
    ms.FontFamily := '''' + ChatWebServers[i].FontStyleDefault + ''';';
    ms.FontFamily := StringReplace(ms.FontFamily, '.ttf', '', [rfReplaceAll]);
    ms.BlockColor := ChatWebServers[i].StyleBlockColor;
    ms.BlockPadding := ChatWebServers[i].StyleBlockPadding;
    ms.MaxMsgCount := ChatWebServers[i].MaxMsg;
    ms.TimeMsg := ChatWebServers[i].TimeMsg;
    ms.BorderSize := ChatWebServers[i].StyleBlockBorderSize;
    ms.BorderColor := ChatWebServers[i].StyleBorderColor;
    ms.BColor := ChatWebServers[i].BColor;
    ChatWebServers[i].WebServerChat.AddMessage(ms);
  end;
end;

function TfrOBS.ReplaceEmotesInMessage(const MessageText,
  EmotesString: string): string;
var
  ProcessedEmotes: TDictionary<string, string>;
  Parts: tstringlist;
  i, ColonPos: integer;
  EmoteID: string;
  Emote: TEmotes;
  ResultText: string;
begin
  result := MessageText;
  if EmotesString.Trim = '' then
    Exit;

  // Используем словарь для отслеживания уже обработанных эмодзи
  ProcessedEmotes := TDictionary<string, string>.Create;
  Parts := tstringlist.Create;
  try
    // Разделяем общую строку смайлов по '/'
    Parts.StrictDelimiter := True;
    Parts.Delimiter := '/';
    Parts.DelimitedText := EmotesString;

    ResultText := MessageText;

    // Обрабатываем каждую часть
    for i := 0 to Parts.Count - 1 do
    begin
      ColonPos := Pos(':', Parts[i]);
      if ColonPos = 0 then
        Continue;

      // Извлекаем ID смайла
      EmoteID := Copy(Parts[i], 1, ColonPos - 1);

      // Пропускаем если уже обрабатывали этот эмодзи
      if ProcessedEmotes.ContainsKey(EmoteID) then
        Continue;

      // Получаем данные смайла
      Emote := FindEmoteByID(EmoteID);
      if Emote.ID = '' then
        Continue;

      var
        imgUrl: string;
      if Emote.topImage <> '' then
        imgUrl := Emote.topImage
      else if Emote.images.Url4x <> '' then
        imgUrl := Emote.images.Url4x
      else
        Continue; // Пропускаем если нет URL

      // Заменяем все вхождения имени эмодзи
      ResultText := StringReplace(ResultText, Emote.name,
        Format('<img src="%s" width="18" height="18">', [imgUrl]),
        [rfReplaceAll]);

      // Помечаем эмодзи как обработанный
      ProcessedEmotes.Add(EmoteID, '');
    end;

    result := ResultText;
  finally
    ProcessedEmotes.Free;
    Parts.Free;
  end;
end;

procedure TfrOBS.AddChat(newRecord: TOBSChat);
begin
  SetLength(listChats, Length(listChats) + 1);
  listChats[High(listChats)] := newRecord;
  UpdateGridFromArray;

  db.SaveRecordArray<TOBSChat>('listChats', listChats);
  CreateWebChat(newRecord);
end;

procedure TfrOBS.AddKandinsky(newRecord: TOBSKandinsky);
begin
  SetLength(listKandinsky, Length(listKandinsky) + 1);
  listKandinsky[High(listKandinsky)] := newRecord;
  UpdateGridFromArray;
  db.SaveRecordArray<TOBSKandinsky>('listKandinsky', listKandinsky);
  CreateWebKandinsky(newRecord);
end;

procedure TfrOBS.AddNotify(newRecord: TOBSNotify);
begin
  SetLength(listNotify, Length(listNotify) + 1);
  listNotify[High(listNotify)] := newRecord;
  UpdateGridFromArray;
  db.SaveRecordArray<TOBSNotify>('listNotify', listNotify);
  CreateWebEvents(newRecord);
end;

procedure TfrOBS.btnCreateOBSChatClick(Sender: TObject);
var
  dport, i: integer;
begin
  dport := 8080;
  for i := 0 to sgWebChats.RowCount - 1 do
  begin
    if strtoint(sgWebChats.Cells[0, i]) >= dport then
      dport := strtoint(sgWebChats.Cells[0, i]) + 1;
  end;
  fCreateChat.sbWebServerPort.Value := dport;
  fCreateChat.isEdit := false;
  fCreateChat.Show;
end;

procedure TfrOBS.btnCreateOBSKandinskyClick(Sender: TObject);
var
  dport: integer;
  i: integer;
  rk: TOBSKandinsky;
begin
  dport := 8080;
  for i := 0 to sgWebChats.RowCount - 1 do
  begin
    if strtoint(sgWebChats.Cells[0, i]) >= dport then
      dport := strtoint(sgWebChats.Cells[0, i]) + 1;
  end;
  rk.port := dport;
  AddKandinsky(rk);

end;

procedure TfrOBS.btnCreateOBSNotifyClick(Sender: TObject);
var
  dport, i: integer;
begin
  dport := 8080;
  for i := 0 to sgWebChats.RowCount - 1 do
  begin
    if strtoint(sgWebChats.Cells[0, i]) >= dport then
      dport := strtoint(sgWebChats.Cells[0, i]) + 1;
  end;
  fCreateNotify.sbWebServerPort.Value := dport;
  fCreateNotify.isEdit := false;
  fCreateNotify.Show;
end;

procedure TfrOBS.btnDeleteeChatClick(Sender: TObject);
begin
  if sgWebChats.Cells[1, sgWebChats.Row] = 'Чат' then
  begin
    DelChat(strtoint(sgWebChats.Cells[0, sgWebChats.Row]));
  end;
  if sgWebChats.Cells[1, sgWebChats.Row] = 'Kandinsky' then
  begin
    DelKandinsky(strtoint(sgWebChats.Cells[0, sgWebChats.Row]));
  end;
  if sgWebChats.Cells[1, sgWebChats.Row] = 'Оповещение' then
  begin
    DelNotify(strtoint(sgWebChats.Cells[0, sgWebChats.Row]));
  end;
end;

procedure TfrOBS.CreateWebChat(chatSettings: TOBSChat);
var
  ChatWebServer: TChatWebServers;
  fonts: tstringlist;
  f: TfrColorSettings;
  t: TfrFontSettings;
  procedure LoadFontList(const mySL: tstringlist);
  var
    SearchRec: TSearchRec;
  begin
    if not DirectoryExists(myConst.fontsPath) then
      CreateDir(myConst.fontsPath);

    if FindFirst(IncludeTrailingPathDelimiter(myConst.fontsPath) + '*.*',
      faArchive, SearchRec) = 0 then
      try
        repeat
          if (SearchRec.Attr and faAnyFile) = SearchRec.Attr then
          begin
            mySL.Add(SearchRec.name);
          end;
        until FindNext(SearchRec) <> 0;
      finally
        System.SysUtils.FindClose(SearchRec);
      end;
  end;

begin
  fonts := tstringlist.Create;
  f := TfrColorSettings.Create(self);
  t := TfrFontSettings.Create(self);
  try
    LoadFontList(fonts);
    ChatWebServer.WebServerChat := TTTW_Chat.Create(fonts, chatSettings.port,
      f.ccbBColor.Items[chatSettings.ColorBackground]);
    ChatWebServer.port := chatSettings.port;
    ChatWebServer.MaxMsg := chatSettings.MaxCountMess;
    ChatWebServer.TimeMsg := chatSettings.TimeMess;
    ChatWebServer.Freez := chatSettings.Freez = 1;
    ChatWebServer.StyleBorderColor := f.ccbStyleBorderColor.Items
      [chatSettings.ColorBorder];
    ChatWebServer.StyleBlockColor := chatSettings.ColorBlock;
    ChatWebServer.StyleBlockBorderSize := chatSettings.SolidBorder;
    ChatWebServer.StyleBlockPadding := chatSettings.Paddings;
    ChatWebServer.FontStyleDefault := t.cbFontStyleDefault.Items
      [chatSettings.StyleFont];
    ChatWebServer.FontColor := t.ccbFontColor.Items[chatSettings.ColorFont];
    ChatWebServer.BColor := f.ccbBColor.Items[chatSettings.ColorBorder];;
    ChatWebServer.FontSize := chatSettings.SizeFont;
    ChatWebServers.Add(ChatWebServer);

    ChatWebServers[ChatWebServers.Count - 1].WebServerChat.ActiveServer(True);
    ChatWebServers[ChatWebServers.Count - 1].WebServerChat.SetDeleteMode
      (not ChatWebServer.Freez, ChatWebServer.MaxMsg);

  finally
    fonts.Free;
    f.Free;
    t.Free;
  end;
end;

procedure TfrOBS.CreateWebEvents(eventsSettings: TOBSNotify);
var
  EventWebServer: TEventWebServers;
  fonts: tstringlist;
  f: TfrColorSettings;
  t: TfrFontSettings;
  procedure LoadFontList(const mySL: tstringlist);
  var
    SearchRec: TSearchRec;
  begin
    if not DirectoryExists(myConst.fontsPath) then
      CreateDir(myConst.fontsPath);
    if FindFirst(IncludeTrailingPathDelimiter(myConst.fontsPath) + '*.*',
      faArchive, SearchRec) = 0 then
      try
        repeat
          if (SearchRec.Attr and faAnyFile) = SearchRec.Attr then
          begin
            mySL.Add(SearchRec.name);
          end;
        until FindNext(SearchRec) <> 0;
      finally
        System.SysUtils.FindClose(SearchRec);
      end;
  end;

  function GetColorFromColorPanel(aColor: TAlphaColor): string;
  var
    Color: TAlphaColor;
    r, G, B: Byte;
    A: Real;
    FS: TFormatSettings;
  begin
    Color := aColor;
    r := TAlphaColorRec(Color).r;
    G := TAlphaColorRec(Color).G;
    B := TAlphaColorRec(Color).B;
    A := TAlphaColorRec(Color).A / 255;
    // Преобразуем альфа-канал в диапазон 0..1

    // Устанавливаем точку в качестве десятичного разделителя
    FS := TFormatSettings.Create;
    FS.DecimalSeparator := '.';

    result := Format('rgba(%d, %d, %d, %.2f)', [r, G, B, A], FS);
  end;

  function GetColorFromCCB(aColor: TAlphaColor): string;
  var
    Color: TAlphaColor;
    r, G, B: Byte;
  begin
    Color := aColor;
    r := TAlphaColorRec(Color).r;
    G := TAlphaColorRec(Color).G;
    B := TAlphaColorRec(Color).B;
    result := Format('#%.2X%.2X%.2X', [r, G, B]);
  end;

begin
  fonts := tstringlist.Create;
  f := TfrColorSettings.Create(self);
  t := TfrFontSettings.Create(self);
  try
    LoadFontList(fonts);
    EventWebServer.WebServerChat := TTTW_Events.Create(fonts,
      eventsSettings.port);
    EventWebServer.port := eventsSettings.port;
    EventWebServer.typeEvent := eventsSettings.typeEvent;
    EventWebServer.se.Title := eventsSettings.HeaderText;
    EventWebServer.se.Context := eventsSettings.MessText;
    EventWebServer.se.Url := eventsSettings.Picture;
    EventWebServer.se.SoundURL := eventsSettings.Sound;
    EventWebServer.se.Timestamp := now;
    EventWebServer.se.TimeMsg := eventsSettings.TimeMess;
    EventWebServer.se.FontTitle.Font := eventsSettings.HeaderStyleFont;
    EventWebServer.se.FontTitle.Size := eventsSettings.HeaderSizeFont;
    EventWebServer.se.FontTitle.Color := eventsSettings.HeaderColorFont;
    EventWebServer.se.FontContext.Font := eventsSettings.MessStyleFont;
    EventWebServer.se.FontContext.Size := eventsSettings.MessSizeFont;
    EventWebServer.se.FontContext.Color :=eventsSettings.MessColorFont;
    EventWebServer.se.BorderColor := eventsSettings.ColorBorder;
    EventWebServer.se.BorderSize := eventsSettings.SolidBorder;
    EventWebServer.se.BlockColor := eventsSettings.ColorBlock;
    EventWebServers.Add(EventWebServer);
    EventWebServers[EventWebServers.Count - 1].WebServerChat.ActiveServer(True);
  finally
    fonts.Free;
    f.Free;
    t.Free;
  end;
end;

procedure TfrOBS.CreateWebKandinsky(aRecord: TOBSKandinsky);
var
  KandinskyWebServer: TKandinskyWebServers;
begin
  KandinskyWebServer.port := aRecord.port;
  KandinskyWebServer.WebServerKandinsky :=
    TKandinsky_Web.Create(TTW_Bot.frAI1.edtKandiKey.text,
    TTW_Bot.frAI1.edtKandiSecret.text, aRecord.port);
  KandinskyWebServers.Add(KandinskyWebServer);
  KandinskyWebServers[KandinskyWebServers.Count - 1]
    .WebServerKandinsky.ActiveServer(True);

end;

procedure TfrOBS.DelChat(aPort: integer);
var
  i, j: integer;
begin
  // Ищем в обратном порядке для безопасного удаления
  for i := High(listChats) downto 0 do
  begin
    if listChats[i].port = aPort then
    begin
      // Сдвигаем элементы массива
      for j := i to High(listChats) - 1 do
        listChats[j] := listChats[j + 1];
      // Уменьшаем размер массива
      SetLength(listChats, Length(listChats) - 1);
      // Выходим после первого найденного совпадения (предполагаем уникальность портов)
      Break;
    end;
  end;
  ChatWebServers[i].WebServerChat.ActiveServer(false);
  ChatWebServers[i].WebServerChat.Destroy;
  ChatWebServers.Delete(i);
  db.SaveRecordArray<TOBSChat>('listChats', listChats);
  UpdateGridFromArray;
end;

procedure TfrOBS.DelKandinsky(aPort: integer);
var
  i, j: integer;
begin
  // Ищем в обратном порядке для безопасного удаления
  for i := High(listKandinsky) downto 0 do
  begin
    if listKandinsky[i].port = aPort then
    begin
      // Сдвигаем элементы массива
      for j := i to High(listKandinsky) - 1 do
        listKandinsky[j] := listKandinsky[j + 1];
      // Уменьшаем размер массива
      SetLength(listKandinsky, Length(listKandinsky) - 1);
      // Выходим после первого найденного совпадения (предполагаем уникальность портов)
      Break;
    end;
  end;
  KandinskyWebServers[i].WebServerKandinsky.ActiveServer(false);
  KandinskyWebServers[i].WebServerKandinsky.Destroy;
  KandinskyWebServers.Delete(i);
  UpdateGridFromArray;
  db.SaveRecordArray<TOBSKandinsky>('listKandinsky', listKandinsky);
end;

procedure TfrOBS.DelNotify(aPort: integer);
var
  i, j: integer;
begin
  // Ищем в обратном порядке для безопасного удаления
  for i := High(listNotify) downto 0 do
  begin
    if listNotify[i].port = aPort then
    begin
      // Сдвигаем элементы массива
      for j := i to High(listNotify) - 1 do
        listNotify[j] := listNotify[j + 1];
      // Уменьшаем размер массива
      SetLength(listNotify, Length(listNotify) - 1);
      // Выходим после первого найденного совпадения (предполагаем уникальность портов)
      Break;
    end;
  end;
  EventWebServers[i].WebServerChat.ActiveServer(false);
  EventWebServers[i].WebServerChat.Destroy;
  EventWebServers.Delete(i);
  UpdateGridFromArray;
  db.SaveRecordArray<TOBSNotify>('listNotify', listNotify);
end;

procedure TfrOBS.EdtChat(newRecord: TOBSChat; oldPort: integer);
var
  i, j: integer;
  chatWeb: TChatWebServers; // Временная переменная для записи
  f: TfrColorSettings;
  t: TfrFontSettings;
begin
  f := TfrColorSettings.Create(self);
  t := TfrFontSettings.Create(self);
  try
    // Обновляем запись в listChats
    for i := 0 to High(listChats) do
      if listChats[i].port = oldPort then
      begin
        listChats[i] := newRecord;
        Break;
      end;

    // Обновляем соответствующий сервер в ChatWebServers
    for j := 0 to ChatWebServers.Count - 1 do
    begin
      // 1. Извлекаем запись во временную переменную
      chatWeb := ChatWebServers[j];

      if chatWeb.port = oldPort then
      begin
        // 2. Модифицируем поля записи
        chatWeb.MaxMsg := newRecord.MaxCountMess;
        chatWeb.TimeMsg := newRecord.TimeMess;
        chatWeb.Freez := newRecord.Freez = 1;
        chatWeb.StyleBorderColor := f.ccbStyleBorderColor.Items
          [newRecord.ColorBorder];
        chatWeb.StyleBlockColor := newRecord.ColorBlock;
        chatWeb.StyleBlockBorderSize := newRecord.SolidBorder;
        chatWeb.StyleBlockPadding := newRecord.Paddings;
        chatWeb.FontStyleDefault := t.cbFontStyleDefault.Items
          [newRecord.StyleFont];
        chatWeb.FontColor := t.ccbFontColor.Items[newRecord.ColorFont];
        chatWeb.FontSize := newRecord.SizeFont;
        chatWeb.BColor := f.ccbBColor.Items[newRecord.ColorBackground];
        chatWeb.WebServerChat.changeBackground
          (f.ccbBColor.Items[newRecord.ColorBackground]);
        chatWeb.WebServerChat.SetDeleteMode(not chatWeb.Freez, chatWeb.MaxMsg);

        // 4. Возвращаем модифицированную запись в список
        ChatWebServers[j] := chatWeb;

        Break;
      end;
    end;

    UpdateGridFromArray;
    db.SaveRecordArray<TOBSChat>('listChats', listChats);
  finally
    f.Free;
    t.Free;
  end;
end;

procedure TfrOBS.EdtNotify(newRecord: TOBSNotify; oldPort: integer);
var
  i,mi: integer; ws:TEventWebServers;
begin
for I := 0 to EventWebServers.Count - 1 do
  if EventWebServers[i].port = oldPort then
  begin
  ws:=EventWebServers[i];
  mi:=i;
  end;

  for i := 0 to High(listNotify) do
    if listNotify[i].port = oldPort then
    begin
      listNotify[i] := newRecord;
      ws.port:=listNotify[i].port;
      ws.typeEvent:=listNotify[i].TypeEvent;
      ws.se.title:=listNotify[i].HeaderText;
      ws.se.Context:=listNotify[i].MessText;
      ws.se.Url:=listNotify[i].Picture;
      ws.se.SoundURL:=listNotify[i].Sound;
      ws.se.TimeMsg:=listNotify[i].TimeMess;
      ws.se.FontTitle.Font:=listNotify[i].HeaderStyleFont;
      ws.se.FontTitle.Font := listNotify[i].HeaderStyleFont;
      ws.se.FontTitle.Size := listNotify[i].HeaderSizeFont;
      ws.se.FontTitle.Color := listNotify[i].HeaderColorFont;
      ws.se.FontContext.Font := listNotify[i].MessStyleFont;
      ws.se.FontContext.Size := listNotify[i].MessSizeFont;
      ws.se.FontContext.Color := listNotify[i].MessColorFont;
      ws.se.BorderColor :=listNotify[i].ColorBorder;
      ws.se.BorderSize := listNotify[i].SolidBorder;
      ws.se.BlockColor := listNotify[i].ColorBlock;
      EventWebServers[mi]:=ws;
      UpdateGridFromArray;
      db.SaveRecordArray<TOBSNotify>('listNotify', listNotify);
      Break;
    end;
end;

function TfrOBS.FindEmoteByID(const ID: string): TEmotes;
var
  i: integer;
begin
  result.ID := '';
  if not Assigned(ChatEmotes) then
    Exit;

  for i := 0 to ChatEmotes.Count - 1 do
    if ChatEmotes[i].ID = ID then
    begin
      result := ChatEmotes[i];
      Break;
    end;
end;

procedure TfrOBS.sgWebChatsCellDblClick(const Column: TColumn;
  const Row: integer);
var
  myChatRec: TOBSChat;
  myNotifyRec: TOBSNotify;
  i: integer;
begin
  if sgWebChats.Cells[1, Row] = 'Оповещение' then
  begin
    for i := 0 to High(listNotify) do
      if listNotify[i].port = (strtoint(sgWebChats.Cells[0, Row])) then
      begin
        myNotifyRec := listNotify[i];
        Break;
      end;

    fCreateNotify.isEdit := True;
    fCreateNotify.setRecord(myNotifyRec);
    fCreateNotify.Show;
  end;
  if sgWebChats.Cells[1, Row] = 'Чат' then
  begin
    for i := 0 to High(listChats) do
      if listChats[i].port = (strtoint(sgWebChats.Cells[0, Row])) then
      begin
        myChatRec := listChats[i];
        Break;
      end;
    fCreateChat.isEdit := True;
    fCreateChat.setRecord(myChatRec);
    fCreateChat.Show;
  end;
end;

{
  0 Отслеживание
  1 Подписка
  2 Подарочная подписка
  3 Рейд
  4 Донат
  5 Свое

}

procedure TfrOBS.toEventWebServer(aEvent: TFollowEvent);
var
  i: integer;
  ws: TEventWebServers;
begin
  TTW_Bot.toLog('fOBS', 'toEventWebServer', 'Получено событие Follow', 0);
  for i := 0 to EventWebServers.Count - 1 do
  begin
    if EventWebServers[i].typeEvent = 0 then
    begin
      ws := EventWebServers[i];
      ws.se.Title := StringReplace(ws.se.Title, '[NICK]',
        aEvent.event.user_name, [rfReplaceAll]);
      ws.se.Timestamp:=now;
      ws.se.RequireInteraction := True;
      TTW_Bot.toLog('fOBS', 'toEventWebServer.BlockColor', ws.se.BlockColor, 3);
      EventWebServers[i].WebServerChat.AddMessage(ws.se);
    end;
  end;
end;

procedure TfrOBS.toEventWebServer(aEvent: TSubEvent);
var
  i: integer;
  ws: TEventWebServers;
begin
  for i := 0 to EventWebServers.Count - 1 do
  begin
    if EventWebServers[i].typeEvent <> 1 then
      Continue;
    ws := EventWebServers[i];
    ws.se.Title := StringReplace(ws.se.Title, '[NICK]', aEvent.event.user_name,
      [rfReplaceAll]);
    EventWebServers[i] := ws;
    EventWebServers[i].WebServerChat.AddMessage(EventWebServers[i].se);
  end;
end;

procedure TfrOBS.toEventWebServer(aEvent: TRaidEvent);
var
  i: integer;
  ws: TEventWebServers;
begin
  for i := 0 to EventWebServers.Count - 1 do
  begin
    if EventWebServers[i].typeEvent <> 3 then
      Continue;
    ws := EventWebServers[i];
    ws.se.Title := StringReplace(ws.se.Title, '[NICK]',
      aEvent.event.from_broadcaster_user_name, [rfReplaceAll]);
    ws.se.Context := StringReplace(ws.se.Context, '[SUM]',
      inttostr(aEvent.event.viewers), [rfReplaceAll]);
    EventWebServers[i] := ws;
    EventWebServers[i].WebServerChat.AddMessage(EventWebServers[i].se);
  end;
end;

procedure TfrOBS.toEventWebServer(aEvent: TGiftEvent);
var
  i: integer;
  ws: TEventWebServers;
begin
  for i := 0 to EventWebServers.Count - 1 do
  begin
    if EventWebServers[i].typeEvent <> 2 then
      Continue;
    ws := EventWebServers[i];
    ws.se.Title := StringReplace(ws.se.Title, '[NICK]', aEvent.event.user_name,
      [rfReplaceAll]);
    EventWebServers[i] := ws;
    EventWebServers[i].WebServerChat.AddMessage(EventWebServers[i].se);
  end;
end;

procedure TfrOBS.toEventWebServer(aNick, aSum, aText: string);
var
  i: integer;
  ws: TEventWebServers;
begin
  for i := 0 to EventWebServers.Count - 1 do
  begin
    if EventWebServers[i].typeEvent <> 4 then
      Continue;
    ws := EventWebServers[i];
    ws.se.Title := StringReplace(ws.se.Title, '[NICK]', aNick, [rfReplaceAll]);
    ws.se.Context := StringReplace(ws.se.Context, '[SUM]', aSum,
      [rfReplaceAll]);
    EventWebServers[i] := ws;
    EventWebServers[i].WebServerChat.AddMessage(EventWebServers[i].se);
  end;

end;

procedure TfrOBS.UpdateGridFromArray;
var
  i, rowIndex: integer;
begin
  sgWebChats.BeginUpdate;
  try
    sgWebChats.RowCount := 0; // Сбрасываем строки

    rowIndex := 0; // Отдельный счетчик для строк сетки

    // listChats
    for i := 0 to High(listChats) do
    begin
      sgWebChats.RowCount := rowIndex + 1;
      sgWebChats.Cells[0, rowIndex] := inttostr(listChats[i].port);
      sgWebChats.Cells[1, rowIndex] := 'Чат';
      sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' +
        inttostr(listChats[i].port);
      Inc(rowIndex); // Увеличиваем счетчик строк
    end;

    // listNotify
    for i := 0 to High(listNotify) do
    begin
      sgWebChats.RowCount := rowIndex + 1;
      sgWebChats.Cells[0, rowIndex] := inttostr(listNotify[i].port);
      sgWebChats.Cells[1, rowIndex] := 'Оповещение';
      sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' +
        inttostr(listNotify[i].port);
      Inc(rowIndex); // Увеличиваем счетчик строк
    end;

    // listKandinsky
    for i := 0 to High(listKandinsky) do
    begin
      sgWebChats.RowCount := rowIndex + 1;
      sgWebChats.Cells[0, rowIndex] := inttostr(listKandinsky[i].port);
      sgWebChats.Cells[1, rowIndex] := 'Kandinsky';
      sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' +
        inttostr(listKandinsky[i].port);
      Inc(rowIndex); // Увеличиваем счетчик строк
    end;
  finally
    sgWebChats.EndUpdate;
  end;
end;

end.
