unit uCustomEmoties;

interface

uses
  Classes, System.Generics.Collections, System.JSON, uRecords, IdHTTP, IdSSLOpenSSL,
  System.Net.HttpClient, System.SysUtils;

  type
  TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object;


type
  TBTTV = class(TObject)
  private
    list: TList<TBTTVr>;
    FOnLog: TOnLog;
    procedure AddEmotesGlobalJson(const JsonStr: string);
    procedure AddEmotesUserJson(const JsonStr: string);
    function GetHTTP(aMethod: string): string;
    procedure toLog(alevel: integer; amethod: string; amessage: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure GetGlobal;
    procedure GetCustom(uid: string);
    function GenerateURL(emoteName: string): string;
    property OnLog: TOnLog read FOnLog write FOnLog;
  end;

type
  T7TV = class(TObject)
  private
    list7: TList<T7TVr>;
    FOnLog: TOnLog;
    procedure AddEmotesGlobalJson(const JsonStr: string);
    procedure AddEmotesUserJson(const JsonStr: string);
    function GetHTTP(aMethod: string): string;
  public
    constructor Create;
    destructor Destroy; override;
    procedure GetGlobal;
    procedure GetCustom(uid: string);
    function GenerateURL(emoteName: string): string;
    procedure toLog(alevel: integer; amethod: string; amessage: string);
    property OnLog: TOnLog read FOnLog write FOnLog;
  end;

implementation

{ TBTTV }

constructor TBTTV.Create;
begin
  inherited;
  list := TList<TBTTVr>.Create;
end;

destructor TBTTV.Destroy;
begin
  FreeAndNil(list);
  inherited;
end;

procedure TBTTV.AddEmotesGlobalJson(const JsonStr: string);
var
  JSONValue: TJSONValue;
  JSONArray: TJSONArray;
  EmoteObj: TJSONObject;
  NewEmote: TBTTVr;
  i: Integer;
begin
  JSONValue := TJSONObject.ParseJSONValue(JsonStr);
  if not Assigned(JSONValue) then Exit;

  try
    if not (JSONValue is TJSONArray) then Exit;
    JSONArray := TJSONArray(JSONValue);

    for i := 0 to JSONArray.Count - 1 do
    begin
      if not (JSONArray.Items[i] is TJSONObject) then Continue;

      EmoteObj := TJSONObject(JSONArray.Items[i]);
      NewEmote := Default(TBTTVr);

      if Assigned(EmoteObj.GetValue('id')) then
        NewEmote.id := EmoteObj.GetValue('id').Value;
      if Assigned(EmoteObj.GetValue('code')) then
        NewEmote.code := EmoteObj.GetValue('code').Value;

      if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then
        list.Add(NewEmote);
    end;
  finally
    JSONValue.Free;
  end;
end;

procedure TBTTV.AddEmotesUserJson(const JsonStr: string);
var
  JSONValue, ChannelEmotes: TJSONValue;
  JSONArray: TJSONArray;
  EmoteObj: TJSONObject;
  NewEmote: TBTTVr;
  i: Integer;
begin
  JSONValue := TJSONObject.ParseJSONValue(JsonStr);
  if not Assigned(JSONValue) then Exit;

  try
    // Обработка channelEmotes
    ChannelEmotes := TJSONObject(JSONValue).GetValue('channelEmotes');
    if (ChannelEmotes is TJSONArray) then
    begin
      JSONArray := TJSONArray(ChannelEmotes);
      for i := 0 to JSONArray.Count - 1 do
      begin
        if not (JSONArray.Items[i] is TJSONObject) then Continue;

        EmoteObj := TJSONObject(JSONArray.Items[i]);
        NewEmote := Default(TBTTVr);

        if Assigned(EmoteObj.GetValue('id')) then
          NewEmote.id := EmoteObj.GetValue('id').Value;
        if Assigned(EmoteObj.GetValue('code')) then
          NewEmote.code := EmoteObj.GetValue('code').Value;

        if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then
          list.Add(NewEmote);
      end;
    end;

    // Обработка sharedEmotes
    ChannelEmotes := TJSONObject(JSONValue).GetValue('sharedEmotes');
    if (ChannelEmotes is TJSONArray) then
    begin
      JSONArray := TJSONArray(ChannelEmotes);
      for i := 0 to JSONArray.Count - 1 do
      begin
        if not (JSONArray.Items[i] is TJSONObject) then Continue;

        EmoteObj := TJSONObject(JSONArray.Items[i]);
        NewEmote := Default(TBTTVr);

        if Assigned(EmoteObj.GetValue('id')) then
          NewEmote.id := EmoteObj.GetValue('id').Value;
        if Assigned(EmoteObj.GetValue('code')) then
          NewEmote.code := EmoteObj.GetValue('code').Value;

        if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then
          list.Add(NewEmote);
      end;
    end;
  finally
    JSONValue.Free;
  end;
end;

function TBTTV.GenerateURL(emoteName: string): string;
var
  emote: TBTTVr;
begin
  Result := '';
  for emote in list do
  begin
    if emote.code = emoteName then
    begin
      Result := 'https://cdn.betterttv.net/emote/' + emote.id + '/1x';
      Exit;
    end;
  end;
end;

procedure TBTTV.GetCustom(uid: string);
begin
  if not uid.IsEmpty then
    AddEmotesUserJson(GetHTTP('users/twitch/' + uid));
end;

procedure TBTTV.GetGlobal;
begin
  AddEmotesGlobalJson(GetHTTP('emotes/global'));
end;

function TBTTV.GetHTTP(aMethod: string): string;
var
  http: TIdHTTP;
  ssl: TIdSSLIOHandlerSocketOpenSSL;
begin
  Result := '';

  try
    http := TIdHTTP.Create(nil);
    ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    try
      http.IOHandler := ssl;
      ssl.SSLOptions.SSLVersions := [sslvTLSv1_2];
      http.Request.UserAgent :=
        'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
      Result := http.Get('https://api.betterttv.net/3/cached/' + aMethod);
    finally
      ssl.Free;
      http.Free;
    end;
  except
    on E: Exception do
    begin
      toLog(2,'GetCustom',e.Message);
      Result := '';
    end;
  end;

end;

procedure TBTTV.toLog(alevel: integer; amethod, amessage: string);
begin
  if Assigned(FOnLog) then
    FOnLog('uCustomEmoties.TBTTV', amethod, amessage, alevel);
end;

{ T7TV }

constructor T7TV.Create;
begin
  inherited;
  list7 := TList<T7TVr>.Create;
end;

destructor T7TV.Destroy;
begin
  FreeAndNil(list7);
  inherited;
end;

procedure T7TV.AddEmotesGlobalJson(const JsonStr: string);
var
  Root: TJSONObject;
  EmotesArray: TJSONArray;
  EmoteObj, DataObj, HostObj: TJSONObject;
  FilesArray: TJSONArray;
  i: Integer;
  Emote: T7TVr;
  BaseUrl: string;
begin
  Root := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject;
  if not Assigned(Root) then Exit;

  try
    EmotesArray := Root.GetValue('emotes') as TJSONArray;
    if not Assigned(EmotesArray) then Exit;

    for i := 0 to EmotesArray.Count - 1 do
    begin
      if not (EmotesArray.Items[i] is TJSONObject) then Continue;

      EmoteObj := EmotesArray.Items[i] as TJSONObject;
      Emote := Default(T7TVr);

      // Получение базовых данных
      if Assigned(EmoteObj.GetValue('id')) then
        Emote.id := EmoteObj.GetValue('id').Value;
      if Assigned(EmoteObj.GetValue('name')) then
        Emote.code := EmoteObj.GetValue('name').Value;

      // Получение URL
      DataObj := EmoteObj.GetValue('data') as TJSONObject;
      if Assigned(DataObj) then
      begin
        HostObj := DataObj.GetValue('host') as TJSONObject;
        if Assigned(HostObj) then
        begin
          if Assigned(HostObj.GetValue('url')) then
          begin
            BaseUrl := 'https:' + HostObj.GetValue('url').Value;

            FilesArray := HostObj.GetValue('files') as TJSONArray;
            if Assigned(FilesArray) and (FilesArray.Count > 0) and
               (FilesArray.Items[0] is TJSONObject) then
            begin
              Emote.url := BaseUrl + '/' +
                (FilesArray.Items[0] as TJSONObject).GetValue('name').Value;
            end;
          end;
        end;
      end;

      if not Emote.id.IsEmpty and not Emote.code.IsEmpty and not Emote.url.IsEmpty then
        list7.Add(Emote);
    end;
  finally
    Root.Free;
  end;
end;

procedure T7TV.AddEmotesUserJson(const JsonStr: string);
var
  Root, EmoteSet, EmoteObj, DataObj, HostObj: TJSONObject;
  EmotesArr, FilesArr: TJSONArray;
  i: Integer;
  Emote: T7TVr;
  BaseUrl: string;
begin
  Root := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject;
  if not Assigned(Root) then Exit;

  try
    if not Root.TryGetValue<TJSONObject>('emote_set', EmoteSet) then Exit;

    EmotesArr := EmoteSet.GetValue('emotes') as TJSONArray;
    if not Assigned(EmotesArr) then Exit;

    for i := 0 to EmotesArr.Count - 1 do
    begin
      if not (EmotesArr.Items[i] is TJSONObject) then Continue;

      EmoteObj := EmotesArr.Items[i] as TJSONObject;
      Emote := Default(T7TVr);

      // Получение базовых данных
      if Assigned(EmoteObj.GetValue('id')) then
        Emote.id := EmoteObj.GetValue('id').Value;
      if Assigned(EmoteObj.GetValue('name')) then
        Emote.code := EmoteObj.GetValue('name').Value;

      // Получение URL
      DataObj := EmoteObj.GetValue('data') as TJSONObject;
      if Assigned(DataObj) then
      begin
        HostObj := DataObj.GetValue('host') as TJSONObject;
        if Assigned(HostObj) then
        begin
          if Assigned(HostObj.GetValue('url')) then
          begin
            BaseUrl := 'https:' + HostObj.GetValue('url').Value;

            FilesArr := HostObj.GetValue('files') as TJSONArray;
            if Assigned(FilesArr) and (FilesArr.Count > 0) and
               (FilesArr.Items[0] is TJSONObject) then
            begin
              Emote.url := BaseUrl + '/' +
                (FilesArr.Items[0] as TJSONObject).GetValue('name').Value;
            end;
          end;
        end;
      end;

      if not Emote.id.IsEmpty and not Emote.code.IsEmpty and not Emote.url.IsEmpty then
        list7.Add(Emote);
    end;
  finally
    Root.Free;
  end;
end;

function T7TV.GenerateURL(emoteName: string): string;
var
  emote: T7TVr;
begin
  Result := '';
  for emote in list7 do
  begin
    if emote.code = emoteName then
    begin
      Result := emote.url;
      Exit;
    end;
  end;
end;

procedure T7TV.GetCustom(uid: string);
begin
  if not uid.IsEmpty then
    AddEmotesUserJson(GetHTTP('users/twitch/' + uid));
end;

procedure T7TV.GetGlobal;
begin
  AddEmotesGlobalJson(GetHTTP('emote-sets/global'));
end;

function T7TV.GetHTTP(aMethod: string): string;
var
  HttpClient: THTTPClient;
  Response: IHTTPResponse;
begin
  Result := '';
  HttpClient := THTTPClient.Create;
  try
    try
      HttpClient.UserAgent := 'Mozilla/5.0';
      Response := HttpClient.Get('https://api.7tv.app/v3/' + aMethod);
      Result := Response.ContentAsString;
    except
      on E: Exception do
      begin
        toLog(2,'GetHTTP',e.Message);
        Result := '';
      end;
    end;
  finally
    HttpClient.Free;
  end;
end;

procedure T7TV.toLog(alevel: integer; amethod, amessage: string);
begin
  if Assigned(FOnLog) then
    FOnLog('uCustomEmoties.T7TV', amethod, amessage, alevel);
end;

end.
