unit uTTWEventSub;

interface

uses
  System.SysUtils, System.JSON, System.Types, System.UITypes, System.Classes,
  WinInet, ComObj, IdException,
  ipwcore, ipwtypes, ipwwsclient, ipwping, idhttp, IdSSLOpenSSL, uRecords,
  fmx.Types, System.Net.HttpClient, System.Net.HttpClientComponent;

type
  TNotifyEvent = procedure(s: string) of object;
  TGetCustomRewardEvent = procedure(s: TCustomRewardEvent) of object;
  TGetFollowEvent = procedure(s: TFollowEvent) of object;
  TGetGiftEvent = procedure(s: TGiftEvent) of object;
  TGetSubEvent = procedure(s: TSubEvent) of object;
  TGetRaidEvent = procedure(s: TRaidEvent) of object;
  TOnLog = procedure(aModul: string; aMethod: string; aMessage: string;
    aLevel: integer) of object;

  TOnStatus = procedure(Sender: TObject; const ConnectionEvent: String;
    StatusCode: integer; const Description: String) of Object;

type
  TTTW_ES = class(TObject)
    FTimer: ttimer;
    wss: TipwWSClient;

  private

    BroadcasterID: string;
    FAccessToken: string;
    FClientID: string;
    FOnError: TNotifyEvent;
    FOnMessage: TNotifyEvent;
    FOnSubOk: TNotifyEvent;
    FOnRAW: TNotifyEvent;
    FOnGetCustomReward: TGetCustomRewardEvent;
    FOnFollow: TGetFollowEvent;
    FOnGift: TGetGiftEvent;
    FOnSub: TGetSubEvent;
    FOnLog: TOnLog;
    FOnRaid: TGetRaidEvent;
    FOnStatus: TOnStatus;
    SW: TWelcomMessage;
    procedure HandleTimer(Sender: TObject);
    procedure ipwWSClient1DataIn(Sender: TObject; DataFormat: integer;
      const Text: string; const TextB: TBytes; EOM, EOL: Boolean);
    procedure ipwWSPing(Sender: TObject; const Payload: String;
      const PayloadB: TBytes; Response: Boolean);
    procedure ipwWSClient1ConnectionStatus(Sender: TObject;
      const ConnectionEvent: String; StatusCode: integer;
      const Description: String);
    procedure ipwWSClientError(Sender: TObject; ErrorCode: integer;
      const Description: string);
    procedure ipwWSClientDisconnected(Sender: TObject; StatusCode: integer;
      const Description: String);
    procedure ipwWSClientHeader(Sender: TObject; const Field: String;
      const Value: String);
    procedure ipwWSClientLog(Sender: TObject; LogLevel: integer;
      const aMessage, aLog: string);
    function subscribeTo(const EventType, Version: string;
      const Condition: string): Boolean;
    procedure subscribe();
    // function ParseRewardRedeemed(const AJsonString: string): TRewardRedeemed;
    procedure EventMSG(const AText: string);
    function ParseWelcomMessage(const JSONString: string): TWelcomMessage;
    function ParseCustomRewardEvent(const JSONString: string)
      : TCustomRewardEvent;
    function ParseFollowEvent(const JSONString: string): TFollowEvent;
    function ParseSubEvent(const JSONString: string): TSubEvent;
    function ParseGiftEvent(const JSONString: string): TGiftEvent;
    function ParseRaidEvent(const JSONString: string): TRaidEvent;
    procedure toLog(aLevel: integer; aMethod: string; aMessage: string);
    function ParseMetadata(const JSONString: string): TMetadata;
  public

    constructor Create(Sender: TObject;
      aTokenWS, aClientID, aBroadcasterID: string);
    destructor Destroy; override;
    procedure Connect();
    procedure Disconnect;

    property OnMessage: TNotifyEvent read FOnMessage write FOnMessage;
    property OnError: TNotifyEvent read FOnError write FOnError;
    property OnSubOk: TNotifyEvent read FOnSubOk write FOnSubOk;
    property OnRAW: TNotifyEvent read FOnRAW write FOnRAW;
    property OnGetCustomReward: TGetCustomRewardEvent read FOnGetCustomReward
      write FOnGetCustomReward;
    property OnStatus: TOnStatus read FOnStatus write FOnStatus;
    property OnFollow: TGetFollowEvent read FOnFollow write FOnFollow;
    property OnSub: TGetSubEvent read FOnSub write FOnSub;
    property OnGift: TGetGiftEvent read FOnGift write FOnGift;
    property OnRaid: TGetRaidEvent read FOnRaid write FOnRaid;
    property OnLog: TOnLog read FOnLog write FOnLog;
  end;

implementation

uses ugeneral;

function SafeGetObj(Parent: TJSONObject; const Name: string): TJSONObject;
begin
  Result := Parent.GetValue<TJSONObject>(Name);
  if not Assigned(Result) then
    raise Exception.CreateFmt('JSON object "%s" not found', [Name]);
end;

function GetStrDef(Obj: TJSONObject; const Name: string;
  Default: string = ''): string;
begin
  if not Obj.TryGetValue(Name, Result) then
    Result := Default;
end;

function SafeGetInt(Parent: TJSONObject; const Name: string): integer;
var
  V: TJSONValue;
begin
  V := Parent.GetValue(Name);
  if Assigned(V) then
    Result := StrToIntDef(V.Value, 0)
  else
    Result := 0;
end;

function SafeGetBool(Parent: TJSONObject; const Name: string): Boolean;
var
  V: TJSONValue;
begin
  V := Parent.GetValue(Name);
  if Assigned(V) then
    Result := SameText(V.Value, 'true')
  else
    Result := False;
end;

procedure TTTW_ES.toLog(aLevel: integer; aMethod: string; aMessage: string);
begin
  if Assigned(FOnLog) then
    FOnLog('uTTWEvenSub', aMethod, aMessage, aLevel);
end;

procedure TTTW_ES.Connect;
begin

  if wss.Connected then
    wss.Disconnect;

  try
    wss.ConnectTo
      ('wss://eventsub.wss.twitch.tv/ws?keepalive_timeout_seconds=60');
    toLog(0, 'Connect', 'Подключение к WebSocket выполнено');
    FTimer.Enabled := True;
  except
    on E: Exception do
      toLog(2, 'Connect', E.Message);
  end;
end;

constructor TTTW_ES.Create(Sender: TObject;
  aTokenWS, aClientID, aBroadcasterID: string);
begin
  FAccessToken := aTokenWS;
  FClientID := aClientID;
  BroadcasterID := aBroadcasterID;

  wss := TipwWSClient.Create(nil);
  wss.Timeout := 30;
  wss.OnPing := ipwWSPing;
  wss.OnDataIn := ipwWSClient1DataIn;
  wss.OnConnectionStatus := ipwWSClient1ConnectionStatus;
  wss.OnError := ipwWSClientError;
  wss.OnLog := ipwWSClientLog;
  wss.OnDisconnected := ipwWSClientDisconnected;
  wss.OnHeader := ipwWSClientHeader;

  FTimer := ttimer.Create(nil);
  FTimer.Interval := 9000;
  FTimer.OnTimer := HandleTimer;
  FTimer.Enabled := False;

  toLog(0, 'Create', 'Инициализация EventSub');
end;

destructor TTTW_ES.Destroy;
begin
  toLog(0, 'Destroy', 'Завершение работы EventSub');
  try
    if Assigned(FTimer) then
      FreeAndNil(FTimer);

    if Assigned(wss) then
    begin
      if wss.Connected then
        Disconnect;
      FreeAndNil(wss);
    end;
  finally
    inherited Destroy;
  end;
end;

procedure TTTW_ES.Disconnect;
begin
  toLog(1, 'Disconnect', 'Отключение от WebSocket');
  try
    if wss.Connected then
      wss.Disconnect;
    FTimer.Enabled := False;
  except
    on E: Exception do
      toLog(2, 'Disconnect', E.ClassName + ': ' + E.Message);
  end;
end;

procedure TTTW_ES.EventMSG(const AText: string);
var
  md: TMetadata;
begin
  TThread.Queue(nil,
    procedure
    begin

      if Assigned(FOnRAW) then
        FOnRAW(AText);

      md := ParseMetadata(AText);
      toLog(0, 'EventMSG', 'Тип сообщения: ' + md.message_type +
        ', Тип подписки: ' + md.subscription_type);

      if md.message_type = 'session_welcome' then
      begin
        toLog(0, 'EventMSG', 'Получен session_welcome');
        SW := ParseWelcomMessage(AText);
        if Assigned(FOnMessage) then
          FOnMessage('Welcome message');
        subscribe;
      end
      else if md.message_type = 'notification' then
      begin
        if Assigned(FOnGetCustomReward) and
          (md.subscription_type = 'channel.channel_points_custom_reward_redemption.add')
        then
          FOnGetCustomReward(ParseCustomRewardEvent(AText));

        if Assigned(FOnFollow) and (md.subscription_type = 'channel.follow')
        then
          FOnFollow(ParseFollowEvent(AText));

        if Assigned(FOnSub) and (md.subscription_type = 'channel.subscribe')
        then
          FOnSub(ParseSubEvent(AText));

        if Assigned(FOnGift) and
          (md.subscription_type = 'channel.subscription.gift') then
          FOnGift(ParseGiftEvent(AText));

        if Assigned(FOnRaid) and (md.subscription_type = 'channel.raid') then
          FOnRaid(ParseRaidEvent(AText));
      end
      else if md.message_type = 'session_keepalive' then
        toLog(3, 'EventMSG', 'Получен keepalive');
    end);
end;

procedure TTTW_ES.HandleTimer(Sender: TObject);
begin
  if wss.Connected then
  begin
    toLog(3, 'HandleTimer', 'Отправка ping');
    wss.Ping;
  end;
end;

procedure TTTW_ES.ipwWSClient1ConnectionStatus(Sender: TObject;
const ConnectionEvent: String; StatusCode: integer; const Description: String);
begin
  toLog(0, 'ConnectionStatus', Format('%s | %d | %s', [ConnectionEvent,
    StatusCode, Description]));
  if Assigned(FOnStatus) then
    FOnStatus(Sender, ConnectionEvent, StatusCode, Description);
end;

procedure TTTW_ES.ipwWSClient1DataIn(Sender: TObject; DataFormat: integer;
const Text: string; const TextB: TBytes; EOM, EOL: Boolean);
begin
  toLog(3, 'ipwWSClient1DataIn', Text);
  EventMSG(Text);
end;

procedure TTTW_ES.ipwWSClientDisconnected(Sender: TObject; StatusCode: integer;
const Description: String);
begin
  toLog(1, 'ipwWSClientDisconnected', Description);
end;

procedure TTTW_ES.ipwWSClientError(Sender: TObject; ErrorCode: integer;
const Description: string);
begin
  toLog(2, 'ipwWSClientError', Format('Код: %d | %s',
    [ErrorCode, Description]));
  if Assigned(FOnError) then
    FOnError(Description);
end;

procedure TTTW_ES.ipwWSClientHeader(Sender: TObject;
const Field, Value: String);
begin
  // toLog(3,  'ipwWSClientHeader',
  // 'Field: ' + Field + ' | Value: ' + Value);

end;

procedure TTTW_ES.ipwWSClientLog(Sender: TObject; LogLevel: integer;
const aMessage, aLog: string);
begin
  // toLog(3,  'ipwWSClientLog', 'Level: ' + IntToStr(LogLevel)
  // + ' | ' + aMessage + ' | ' + aLog);
  // form1.log(1, 'ipwWSClientLog', 'Level: ' + inttostr(LogLevel) + ' Message: ' +
  // aMessage + ' Log: ' + aLog);

end;

procedure TTTW_ES.ipwWSPing(Sender: TObject; const Payload: String;
const PayloadB: TBytes; Response: Boolean);
begin
  toLog(3, 'ipwWSPing', 'PING ' + Payload);
end;

function TTTW_ES.ParseMetadata(const JSONString: string): TMetadata;
var
  Root, Metadata: TJSONObject;
begin
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Metadata := SafeGetObj(Root, 'metadata');
    Result.message_id := GetStrDef(Metadata, 'message_id');
    Result.message_type := GetStrDef(Metadata, 'message_type');
    Result.message_timestamp := GetStrDef(Metadata, 'message_timestamp');
    Result.subscription_type := GetStrDef(Metadata, 'subscription_type');
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseWelcomMessage(const JSONString: string): TWelcomMessage;
var
  Root, Payload, Session: TJSONObject;
begin
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Session := SafeGetObj(Payload, 'session');
    Result.Payload.Session.id := GetStrDef(Session, 'id');
    Result.Payload.Session.status := GetStrDef(Session, 'status');
    Result.Payload.Session.connected_at := GetStrDef(Session, 'connected_at');
    Result.Payload.Session.keepalive_timeout_seconds :=
      SafeGetInt(Session, 'keepalive_timeout_seconds');
    Result.Payload.Session.reconnect_url := GetStrDef(Session, 'reconnect_url');
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseCustomRewardEvent(const JSONString: string)
  : TCustomRewardEvent;
var
  Root, Payload, Subscription, mCondition, mTransport, Event,
    mReward: TJSONObject;
begin
  toLog(3, 'ParseCustomRewardEvent', 'Начало парсинга награды');
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Subscription := SafeGetObj(Payload, 'subscription');
    with Result.Subscription do
    begin
      id := GetStrDef(Subscription, 'id');
      subscription_type := GetStrDef(Subscription, 'type');
      Version := GetStrDef(Subscription, 'version');
      status := GetStrDef(Subscription, 'status');
      cost := SafeGetInt(Subscription, 'cost');
      created_at := GetStrDef(Subscription, 'created_at');
      mCondition := SafeGetObj(Subscription, 'condition');
      Condition.broadcaster_user_id :=
        GetStrDef(mCondition, 'broadcaster_user_id');
      Condition.reward_id := GetStrDef(mCondition, 'reward_id');
      mTransport := SafeGetObj(Subscription, 'transport');
      transport.method := GetStrDef(mTransport, 'method');
    end;

    Event := SafeGetObj(Payload, 'event');
    with Result.Event do
    begin
      id := GetStrDef(Event, 'id');
      broadcaster_user_id := GetStrDef(Event, 'broadcaster_user_id');
      broadcaster_user_login := GetStrDef(Event, 'broadcaster_user_login');
      broadcaster_user_name := GetStrDef(Event, 'broadcaster_user_name');
      user_id := GetStrDef(Event, 'user_id');
      user_login := GetStrDef(Event, 'user_login');
      user_name := GetStrDef(Event, 'user_name');
      user_input := GetStrDef(Event, 'user_input');
      mReward := SafeGetObj(Event, 'reward');
      revard.id := GetStrDef(mReward, 'id');
      revard.title := GetStrDef(mReward, 'title');
      revard.cost := SafeGetInt(mReward, 'cost');
      revard.prompt := GetStrDef(mReward, 'prompt');
    end;
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseFollowEvent(const JSONString: string): TFollowEvent;
var
  Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject;
begin
  toLog(3, 'ParseFollowEvent', 'Парсинг подписки');
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Subscription := SafeGetObj(Payload, 'subscription');
    with Result.Subscription do
    begin
      id := GetStrDef(Subscription, 'id');
      subscription_type := GetStrDef(Subscription, 'type');
      Version := GetStrDef(Subscription, 'version');
      status := GetStrDef(Subscription, 'status');
      cost := SafeGetInt(Subscription, 'cost');
      created_at := GetStrDef(Subscription, 'created_at');
      mCondition := SafeGetObj(Subscription, 'condition');
      Condition.broadcaster_user_id :=
        GetStrDef(mCondition, 'broadcaster_user_id');
      mTransport := SafeGetObj(Subscription, 'transport');
      transport.method := GetStrDef(mTransport, 'method');
    end;

    Event := SafeGetObj(Payload, 'event');
    with Result.Event do
    begin
      broadcaster_user_id := GetStrDef(Event, 'broadcaster_user_id');
      broadcaster_user_login := GetStrDef(Event, 'broadcaster_user_login');
      broadcaster_user_name := GetStrDef(Event, 'broadcaster_user_name');
      user_id := GetStrDef(Event, 'user_id');
      user_login := GetStrDef(Event, 'user_login');
      user_name := GetStrDef(Event, 'user_name');
      followed_at := GetStrDef(Event, 'followed_at');
    end;
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseGiftEvent(const JSONString: string): TGiftEvent;
var
  Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject;
begin
  toLog(3, 'ParseGiftEvent', 'Парсинг подарочной подписки');
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Subscription := SafeGetObj(Payload, 'subscription');
    with Result.Subscription do
    begin
      id := GetStrDef(Subscription, 'id');
      subscription_type := GetStrDef(Subscription, 'type');
      Version := GetStrDef(Subscription, 'version');
      status := GetStrDef(Subscription, 'status');
      cost := SafeGetInt(Subscription, 'cost');
      created_at := GetStrDef(Subscription, 'created_at');
      mCondition := SafeGetObj(Subscription, 'condition');
      Condition.broadcaster_user_id :=
        GetStrDef(mCondition, 'broadcaster_user_id');
      mTransport := SafeGetObj(Subscription, 'transport');
      transport.method := GetStrDef(mTransport, 'method');
    end;

    Event := SafeGetObj(Payload, 'event');
    with Result.Event do
    begin
      broadcaster_user_id := GetStrDef(Event, 'broadcaster_user_id');
      broadcaster_user_login := GetStrDef(Event, 'broadcaster_user_login');
      broadcaster_user_name := GetStrDef(Event, 'broadcaster_user_name');
      user_id := GetStrDef(Event, 'user_id');
      user_login := GetStrDef(Event, 'user_login');
      user_name := GetStrDef(Event, 'user_name');
      total := SafeGetInt(Event, 'total');
      tier := GetStrDef(Event, 'tier');
      cumulative_total := SafeGetInt(Event, 'cumulative_total');
      is_anonymous := SafeGetBool(Event, 'anonymous');
    end;
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseRaidEvent(const JSONString: string): TRaidEvent;
var
  Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject;
begin
  toLog(3, 'ParseRaidEvent', 'Парсинг рейда');
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Subscription := SafeGetObj(Payload, 'subscription');
    with Result.Subscription do
    begin
      id := GetStrDef(Subscription, 'id');
      subscription_type := GetStrDef(Subscription, 'type');
      Version := GetStrDef(Subscription, 'version');
      status := GetStrDef(Subscription, 'status');
      cost := SafeGetInt(Subscription, 'cost');
      created_at := GetStrDef(Subscription, 'created_at');
      mCondition := SafeGetObj(Subscription, 'condition');
      Condition.broadcaster_user_id :=
        GetStrDef(mCondition, 'to_broadcaster_user_id');
      mTransport := SafeGetObj(Subscription, 'transport');
      transport.method := GetStrDef(mTransport, 'method');
    end;

    Event := SafeGetObj(Payload, 'event');
    with Result.Event do
    begin
      from_broadcaster_user_id := GetStrDef(Event, 'from_broadcaster_user_id');
      from_broadcaster_user_login :=
        GetStrDef(Event, 'from_broadcaster_user_login');
      from_broadcaster_user_name :=
        GetStrDef(Event, 'from_broadcaster_user_name');
      to_broadcaster_user_id := GetStrDef(Event, 'to_broadcaster_user_id');
      to_broadcaster_user_login :=
        GetStrDef(Event, 'to_broadcaster_user_login');
      to_broadcaster_user_name := GetStrDef(Event, 'to_broadcaster_user_name');
      viewers := SafeGetInt(Event, 'viewers');
    end;
  finally
    Root.Free;
  end;
end;

function TTTW_ES.ParseSubEvent(const JSONString: string): TSubEvent;
var
  Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject;
begin
  toLog(3, 'ParseSubEvent', 'Парсинг подписки');
  Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject;
  if not Assigned(Root) then
    raise Exception.Create('Invalid JSON');
  try
    Payload := SafeGetObj(Root, 'payload');
    Subscription := SafeGetObj(Payload, 'subscription');
    with Result.Subscription do
    begin
      id := GetStrDef(Subscription, 'id');
      subscription_type := GetStrDef(Subscription, 'type');
      Version := GetStrDef(Subscription, 'version');
      status := GetStrDef(Subscription, 'status');
      cost := SafeGetInt(Subscription, 'cost');
      created_at := GetStrDef(Subscription, 'created_at');
      mCondition := SafeGetObj(Subscription, 'condition');
      Condition.broadcaster_user_id :=
        GetStrDef(mCondition, 'broadcaster_user_id');
      mTransport := SafeGetObj(Subscription, 'transport');
      transport.method := GetStrDef(mTransport, 'method');
    end;

    Event := SafeGetObj(Payload, 'event');
    with Result.Event do
    begin
      broadcaster_user_id := GetStrDef(Event, 'broadcaster_user_id');
      broadcaster_user_login := GetStrDef(Event, 'broadcaster_user_login');
      broadcaster_user_name := GetStrDef(Event, 'broadcaster_user_name');
      user_id := GetStrDef(Event, 'user_id');
      user_login := GetStrDef(Event, 'user_login');
      user_name := GetStrDef(Event, 'user_name');
      tier := GetStrDef(Event, 'tier');
      is_gift := SafeGetBool(Event, 'is_gift');
    end;
  finally
    Root.Free;
  end;
end;

function TTTW_ES.subscribeTo(const EventType, Version: string;
const Condition: string): Boolean;
var
  JSON: TStringStream;
  Resp: string;
  HTTP: TNetHTTPClient;
begin
  Result := False;
  toLog(0, 'subscribeTo', 'Подписка на ' + EventType);
  try
    HTTP := TNetHTTPClient.Create(nil);
    try
      HTTP.ContentType := 'application/json';
      HTTP.CustomHeaders['Authorization'] := 'Bearer ' + FAccessToken;
      HTTP.CustomHeaders['Client-Id'] := FClientID;

      JSON := TStringStream.Create(TJSONObject.Create.AddPair('type', EventType)
        .AddPair('version', Version).AddPair('condition',
        TJSONObject.ParseJSONValue(Condition) as TJSONObject)
        .AddPair('transport', TJSONObject.Create.AddPair('method', 'websocket')
        .AddPair('session_id', SW.Payload.Session.id)).ToJSON, TEncoding.UTF8);
      try
        Resp := HTTP.Post('https://api.twitch.tv/helix/eventsub/subscriptions',
          JSON).ContentAsString();
        toLog(3, 'subscribeTo', 'Ответ Twitch: ' + Resp);

        if Pos('"status":"enabled"', Resp) > 0 then
        begin
          toLog(0, 'subscribeTo', 'Подписка успешна');
          Result := True;
        end
        else
          toLog(1, 'subscribeTo', 'Подписка не подтверждена: ' + Resp);
      finally
        JSON.Free;
      end;

    finally
      HTTP.Free;
    end;
  except
    on E: Exception do
      toLog(2, 'subscribeTo', 'Ошибка подписки: ' + E.Message);
  end;
end;

procedure TTTW_ES.subscribe;
begin
  // channel.channel_points_custom_reward.add (1)
  // channel.follow   (2)          moderator:read:followers
  // channel.subscribe (1)         channel:read:subscriptions
  // channel.subscription.gift (1) channel:read:subscriptions
  // channel.raid  (1)
  if subscribeTo('channel.channel_points_custom_reward_redemption.add', '1',
    '{"broadcaster_user_id":"' + BroadcasterID + '"}') then
    toLog(0, 'subscribe',
      'channel.channel_points_custom_reward_redemption.add OK')
  else
    toLog(2, 'subscribe',
      'channel.channel_points_custom_reward_redemption.add');

  if subscribeTo('channel.raid', '1', '{"to_broadcaster_user_id":"' +
    BroadcasterID + '"}') then
    toLog(0, 'subscribe', 'channel.raid OK')
  else
    toLog(2, 'subscribe', 'channel.raid');

  if subscribeTo('channel.follow', '2', '{"broadcaster_user_id":"' +
    BroadcasterID + '","moderator_user_id":"' + BroadcasterID + '"}') then
    toLog(0, 'subscribe', 'channel.follow OK')
  else
    toLog(2, 'subscribe', 'channel.follow');

  if subscribeTo('channel.subscribe', '1', '{"broadcaster_user_id":"' +
    BroadcasterID + '"}') then
    toLog(0, 'subscribe', 'channel.subscribe OK')
  else
    toLog(2, 'subscribe', 'channel.subscribe');

  if subscribeTo('channel.subscription.gift', '1', '{"broadcaster_user_id":"' +
    BroadcasterID + '"}') then
    toLog(0, 'subscribe', 'channel.subscription.gift OK')
  else
    toLog(2, 'subscribe', 'channel.subscription.gift');
end;

end.
