ttw_fmx_v10/Services/uTTWIRC.pas

365 lines
9.7 KiB
Plaintext
Raw Permalink Blame History

unit uTTWIRC;
interface
uses
System.Classes, System.SysUtils, IdIRC, IdSSLOpenSSL, IdContext,
FMX.Forms, IdGlobal, IdComponent, System.StrUtils, uRecords;
type
TNotifyEvent = procedure(s: string) of object;
TJoinEvent = procedure(aNick: string) of object;
TMyStatusEvent = procedure(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string) of object;
tOnMessageRecord = procedure(aRecord: TTwitchChatMessage) of object;
TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object;
TTTW = class
private
ws: TIdIRC;
ssl: TIdSSLIOHandlerSocketOpenSSL;
FOnLog: TOnLog;
FOnStatus: TMyStatusEvent;
FOnDisConnect: TNotifyEvent;
FOnJoin: TJoinEvent;
FOnMessage: TNotifyEvent;
FOnMessageRecord: tOnMessageRecord;
channel_name: string;
room_id: string;
channel_id: string;
procedure wsConnected(Sender: TObject);
procedure wsStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
procedure wsDisconnected(Sender: TObject);
procedure wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string);
procedure Join(ASender: TIdContext; const ANickname, AHost, AChannel: string);
procedure se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String);
procedure RAW(text: string);
procedure toLog(aLevel: integer; aMethod: string; aMessage: string);
procedure toParse(t: string);
public
constructor Create(Sender: TObject);
destructor Destroy; override;
procedure Init(a_oauth, a_channel, a_username: string);
procedure Connect;
procedure Disconnect;
procedure sendMessage(text: string);
function ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage;
function GetRoom_ID: string;
function Pars(T_, text, _T: string): string;
property OnLog: TOnLog read FOnLog write FOnLog;
property OnStatus: TMyStatusEvent read FOnStatus write FOnStatus;
property OnDisConnect: TNotifyEvent read FOnDisConnect write FOnDisConnect;
property OnJoin: TJoinEvent read FOnJoin write FOnJoin;
property OnMessage: TNotifyEvent read FOnMessage write FOnMessage;
property OnMessageRecord: tOnMessageRecord read FOnMessageRecord write FOnMessageRecord;
end;
implementation
uses uGeneral; // <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Log
const
LOG_INFO = 0;
LOG_WARNING = 1;
LOG_ERROR = 2;
LOG_DEBUG = 3;
procedure TTTW.toLog(aLevel: integer; aMethod: string; aMessage: string);
begin
if aLevel < 0 then
aLevel := LOG_INFO
else if aLevel > LOG_DEBUG then
aLevel := LOG_DEBUG;
if Assigned(FOnLog) then
FOnLog('uTTWIRC', aMethod, aMessage, aLevel);
end;
constructor TTTW.Create(Sender: TObject);
begin
try
ws := TIdIRC.Create;
ssl := TIdSSLIOHandlerSocketOpenSSL.Create;
ws.IOHandler := ssl;
ws.OnConnected := wsConnected;
ws.OnDisconnected := wsDisconnected;
ws.OnStatus := wsStatus;
ws.OnRaw := wsDataIn;
ws.OnJoin := Join;
ws.OnServerError := se;
except
on E: Exception do
toLog(LOG_ERROR, 'Create', E.Message);
end;
end;
destructor TTTW.Destroy;
begin
try
if Assigned(ws) then
begin
ws.OnConnected := nil;
ws.OnDisconnected := nil;
ws.OnStatus := nil;
ws.OnRaw := nil;
ws.OnJoin := nil;
ws.OnServerError := nil;
ws.IOHandler := nil;
ws.Free;
end;
if Assigned(ssl) then
ssl.Free;
except
on E: Exception do
;
end;
inherited;
end;
function TTTW.ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage;
var
s: string;
LSpacePos: Integer;
LParamStr, LRestStr: string;
LParams: TArray<string>;
I: Integer;
LKeyValue: TArray<string>;
LUsernamePart: string;
LMessagePos: Integer;
begin
Result := Default(TTwitchChatMessage);
s := AMessage;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LSpacePos := Pos(' ', s);
if LSpacePos = 0 then
Exit;
LParamStr := Copy(s, 1, LSpacePos - 1);
LRestStr := Copy(s, LSpacePos + 1, Length(s) - LSpacePos);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LParams := LParamStr.Split([';']);
for I := 0 to High(LParams) do
begin
LKeyValue := LParams[I].Split(['=']);
if Length(LKeyValue) = 2 then
begin
case AnsiIndexStr(LKeyValue[0], [
'@badge-info', 'badges', 'client-nonce', 'color', 'display-name', 'emotes',
'first-msg', 'id', 'mod', 'returning-chatter', 'room-id', 'subscriber',
'tmi-sent-ts', 'turbo', 'user-id', 'user-type', 'vip'
]) of
0: Result.BadgeInfo := LKeyValue[1];
1: Result.Badges := LKeyValue[1];
2: Result.ClientNonce := LKeyValue[1];
3: Result.Color := LKeyValue[1];
4: Result.DisplayName := LKeyValue[1];
5: Result.Emotes := LKeyValue[1];
6: Result.FirstMsg := StrToIntDef(LKeyValue[1], 0);
7: Result.Id := LKeyValue[1];
8: Result.Moder := StrToIntDef(LKeyValue[1], 0);
9: Result.ReturningChatter := StrToIntDef(LKeyValue[1], 0);
10: Result.RoomId := LKeyValue[1];
11: Result.Subscriber := StrToIntDef(LKeyValue[1], 0);
12: Result.TmiSentTs := StrToInt64Def(LKeyValue[1], 0);
13: Result.Turbo := StrToIntDef(LKeyValue[1], 0);
14: Result.UserId := LKeyValue[1];
15: Result.UserType := LKeyValue[1];
16: Result.Vip := StrToIntDef(LKeyValue[1], 0);
end;
end;
end;
if LRestStr.StartsWith(':') then
begin
LUsernamePart := Copy(LRestStr, 1, Pos('!', LRestStr) - 1);
Result.Username := LUsernamePart.Substring(1);
end
else
Result.Username := '';
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
LMessagePos := Pos('PRIVMSG #', LRestStr);
if LMessagePos > 0 then
begin
Inc(LMessagePos, Length('PRIVMSG #'));
Result.Channel := Copy(LRestStr, LMessagePos, PosEx(' ', LRestStr, LMessagePos) - LMessagePos);
end
else
Result.Channel := '';
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
LMessagePos := Pos(' :', LRestStr);
if LMessagePos > 0 then
Result.Message := Copy(LRestStr, LMessagePos + 2, Length(LRestStr) - LMessagePos - 1)
else
Result.Message := '';
end;
procedure TTTW.Init(a_oauth, a_channel, a_username : string);
begin
try
channel_name := a_channel;
ws.Host := 'irc.chat.twitch.tv';
ws.Port := 6697;
ssl.SSLOptions.SSLVersions := [sslvSSLv23];
ws.Password := 'oauth:' + a_oauth;
ws.Nickname := a_username;
channel_name := a_channel;
// Token := a_oauth;
except
on E: Exception do
toLog(LOG_ERROR, 'Init', E.Message);
end;
end;
procedure TTTW.Connect;
begin
try
if not ws.Connected then
begin
ws.Connect;
ws.Raw('CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands');
ws.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
except
on E: Exception do
toLog(LOG_ERROR, 'Connect', E.Message);
end;
end;
procedure TTTW.Disconnect;
begin
try
if ws.Connected then
begin
ws.Disconnect;
end;
except
on E: Exception do
toLog(LOG_ERROR, 'Disconnect', E.ClassName + ': ' + E.Message);
end;
end;
function TTTW.GetRoom_ID: string;
begin
result:=room_id;
end;
procedure TTTW.sendMessage(text: string);
begin
try
ws.Say('#' + channel_name, text);
except
on E: Exception do
toLog(LOG_ERROR, 'sendMessage', E.Message);
end;
end;
procedure TTTW.RAW(text: string);
begin
try
ws.Raw(text);
except
on E: Exception do
toLog(LOG_ERROR, 'RAW', E.Message);
end;
end;
procedure TTTW.wsConnected(Sender: TObject);
begin
if Assigned(FOnStatus) then
FOnStatus(ws, TIdStatus.hsConnected, 'Connected to Twitch IRC');
toLog(LOG_INFO, 'wsConnected', 'Connected to Twitch IRC');
end;
procedure TTTW.wsStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
begin
if Assigned(FOnStatus) then
FOnStatus(ASender, AStatus, AStatusText);
end;
procedure TTTW.wsDisconnected(Sender: TObject);
begin
if Assigned(FOnDisConnect) then
FOnDisConnect('Disconnected');
toLog(LOG_WARNING, 'wsDisconnected', 'Disconnected from Twitch IRC');
end;
procedure TTTW.wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string);
begin
toLog(LOG_DEBUG, 'wsDataIn', AMessage);
if Pos('CAP * ACK', AMessage) <> 0 then
begin
Sleep(200);
ws.Raw('JOIN #' + channel_name);
end;
toParse(AMessage);
end;
procedure TTTW.toParse(t: string);
var
LTwitchChatMessage:tTwitchChatMessage;
begin
try
if (Pos('room-id=', t) <> 0) and (Pos('ROOMSTATE', t) <> 0) then
room_id := Pars('room-id=', t, ';');
if Pos('NOTICE * :Login authentication failed', t) <> 0 then
begin
toLog(2, 'toParse', 'Токен бота просрочен');
Disconnect;
Exit;
end;
if Pos('PRIVMSG', t) <> 0 then
begin
LTwitchChatMessage := ParseTwitchChatMessage(t);
if Assigned(FOnMessageRecord) then
FOnMessageRecord(LTwitchChatMessage);
end;
except
on E: Exception do
toLog(2, 'toParse', E.Message);
end;
end;
procedure TTTW.Join(ASender: TIdContext; const ANickname, AHost, AChannel: string);
begin
if Assigned(FOnJoin) then
FOnJoin(ANickname);
toLog(LOG_INFO, 'Join', ANickname + ' joined ' + AChannel);
end;
procedure TTTW.se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String);
begin
toLog(LOG_ERROR, 'se', AErrorMessage);
end;
function TTTW.Pars(T_, text, _T: string): string;
var
a, b: Integer;
begin
Result := '';
if (T_ = '') or (text = '') or (_T = '') then
Exit;
a := Pos(T_, text);
if a = 0 then
Exit
else
a := a + Length(T_);
text := Copy(text, a, Length(text) - a + 1);
b := Pos(_T, text);
if b > 0 then
Result := Copy(text, 1, b - 1);
end;
end.