реструктуризация файлов, добавление вебчатов

This commit is contained in:
PC1\PTyTb
2025-08-14 10:50:33 +03:00
parent 04b5259737
commit 3ac578b6e6
79 changed files with 10256 additions and 1284 deletions
+135
View File
@@ -0,0 +1,135 @@
unit uAPIDA;
interface
uses
Classes, SysUtils, System.JSON, IdHTTP, IdSSLOpenSSL, flog;
type
TAPIClient = class(TObject)
private
FHttpClient: TIdHTTP;
FSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
FToken: string;
procedure SetToken(const Value: string);
procedure CheckHTTPError(AResponseCode: Integer; const AResponse: string);
public
constructor Create;
destructor Destroy; override;
function GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string;
function GetUserInfo: TJSONObject;
function SubscribeToChannel(const uid, clientUID: string): TJSONObject;
property Token: string read FToken write SetToken;
end;
implementation
const
ContentType = 'application/x-www-form-urlencoded';
UserAgent = 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
AuthorizationHeader = 'Authorization: Bearer ';
constructor TAPIClient.Create;
begin
inherited;
FHttpClient := TIdHTTP.Create(nil);
// ñîçäà¸ì SSL handler áåç âëàäåëüöà — ÿâíîå óïðàâëåíèå
FSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
FSSLHandler.SSLOptions.Method := sslvSSLv23;
FHttpClient.IOHandler := FSSLHandler;
FHttpClient.Request.UserAgent := UserAgent;
FHttpClient.Request.ContentType := ContentType;
FHttpClient.HandleRedirects := True;
end;
destructor TAPIClient.Destroy;
begin
// Îòêëþ÷àåì è îñâîáîæäàåì â áåçîïàñíîì ïîðÿäêå
try
if Assigned(FHttpClient) then
begin
try
// åñëè íóæíî — ïðåðâàòü àêòèâíûå ñîåäèíåíèÿ
except
end;
end;
finally
// Ñíà÷àëà îñâîáîæäàåì IOHandler (åñëè îí íå ïðèíàäëåæèò FHttpClient)
FreeAndNil(FSSLHandler);
FreeAndNil(FHttpClient);
inherited;
end;
end;
procedure TAPIClient.CheckHTTPError(AResponseCode: Integer; const AResponse: string);
begin
if AResponseCode <> 200 then
raise Exception.CreateFmt('HTTP Error %d: %s', [AResponseCode, AResponse]);
end;
function TAPIClient.GetAccessToken(const client_id, client_secret, redirect_uri, code: string): string;
var
Response: string;
Stream: TStringStream;
Json: TJSONObject;
begin
Stream := TStringStream.Create(
Format('grant_type=authorization_code&client_id=%s&client_secret=%s&redirect_uri=%s&code=%s',
[client_id, client_secret, redirect_uri, code]), TEncoding.UTF8);
try
Response := FHttpClient.Post('https://www.donationalerts.com/oauth/token', Stream);
CheckHTTPError(FHttpClient.ResponseCode, Response);
Json := TJSONObject.ParseJSONValue(Response) as TJSONObject;
try
Result := Json.GetValue<string>('access_token');
FToken := Result;
finally
Json.Free;
end;
finally
Stream.Free;
end;
end;
function TAPIClient.GetUserInfo: TJSONObject;
var
Response: string;
begin
FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken);
try
Response := FHttpClient.Get('https://www.donationalerts.com/api/v1/user/oauth');
CheckHTTPError(FHttpClient.ResponseCode, Response);
Result := TJSONObject.ParseJSONValue(Response) as TJSONObject;
finally
FHttpClient.Request.CustomHeaders.Clear;
end;
end;
function TAPIClient.SubscribeToChannel(const uid, clientUID: string): TJSONObject;
var
Response: string;
Stream: TStringStream;
RequestJSON: string;
begin
RequestJSON := Format('{"channels":["$alerts:donation_%s"], "client":"%s"}', [uid, clientUID]);
Stream := TStringStream.Create(RequestJSON, TEncoding.UTF8);
try
FHttpClient.Request.CustomHeaders.Add(AuthorizationHeader + FToken);
FHttpClient.Request.ContentType := 'application/json';
Response := FHttpClient.Post('https://www.donationalerts.com/api/v1/centrifuge/subscribe', Stream);
CheckHTTPError(FHttpClient.ResponseCode, Response);
Result := TJSONObject.ParseJSONValue(Response) as TJSONObject;
finally
Stream.Free;
FHttpClient.Request.CustomHeaders.Clear;
end;
end;
procedure TAPIClient.SetToken(const Value: string);
begin
FToken := Value;
end;
end.
+195
View File
@@ -0,0 +1,195 @@
unit uChatAPI;
interface
uses
Classes, SysUtils, IdHTTP, System.JSON, IdSSLOpenSSL, IdGlobal;
type
TMessage = procedure(s: string) of object;
type
TChatAPI = class(TObject)
protected
FToken_api: string;
FPrefix: string;
FOnError: TMessage;
function GetOtvetFromJson(jsonString: string; isOllama: boolean = false)
: string; virtual;
function CreateHTTPRequest(const url: string; const params: TStringStream;
isOllama: boolean = false): string;
public
constructor Create(Sender: TObject; aToken: string;
aprefix: string = ''); virtual;
destructor Destroy; override;
function GetGPTRequest(url: string; model: string; q: string;
isOllama: boolean = false): string;
property OnError: TMessage read FOnError write FOnError;
end;
implementation
{ TChatAPI }
constructor TChatAPI.Create(Sender: TObject; aToken: string;
aprefix: string = '');
begin
FPrefix := aprefix;
FToken_api := aToken;
end;
function TChatAPI.CreateHTTPRequest(const url: string;
const params: TStringStream; isOllama: boolean = false): string;
var
http: TIdHTTP;
ssl: TIdSSLIOHandlerSocketOpenSSL;
otv: string;
begin
http := TIdHTTP.Create(nil);
ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
http.IOHandler := ssl;
ssl.SSLOptions.method := sslvSSLv23;
http.Request.UserAgent :=
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
http.Request.CustomHeaders.Clear;
// http.Request.CustomHeaders.Add('Content-Type: application/json; charset=utf-8');
http.Request.ContentType := 'application/json; charset=utf-8';
if FToken_api <> '' then
http.Request.CustomHeaders.Add('Authorization: Bearer ' + FToken_api);
http.Request.Accept := 'application/json; charset=utf-8';
http.Request.CharSet := 'utf-8';
http.Response.CharSet := 'utf-8';
// http.Request.CustomHeaders.Add('Accept: application/json; charset=utf-8');
http.Response.ContentEncoding := 'utf-8';
http.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
http.Request.ContentEncoding := 'utf-8';
try
otv := http.Post(url, params);
Result := GetOtvetFromJson(otv, isOllama);
except
on E: Exception do
if Assigned(OnError) then
OnError(E.Message);
end;
finally
params.Free;
http.Free;
ssl.Free;
end;
end;
destructor TChatAPI.Destroy;
begin
inherited;
end;
function ReplaceDelphiHexCodes(const InputStr: string): string;
var
I, Start, HexVal: Integer;
HexStr: string;
begin
Result := '';
I := 1;
while I <= Length(InputStr) do
begin
if (I <= Length(InputStr) - 5) and (InputStr[I] = '#') and
(InputStr[I + 1] = '$') then
begin
HexStr := Copy(InputStr, I + 2, 4);
if TryStrToInt('$' + HexStr, HexVal) then
begin
Result := Result + WideChar(HexVal);
Inc(I, 6); // Ïðîïóñêàåì #$XXXX
Continue;
end;
end;
Result := Result + InputStr[I];
Inc(I);
end;
end;
function ConvertAnsiToUtf8(const AStr: string): string;
var
AnsiBytes: TBytes;
begin
AnsiBytes := TEncoding.ANSI.GetBytes(AStr);
Result := TEncoding.UTF8.GetString(AnsiBytes);
end;
function TChatAPI.GetOtvetFromJson(jsonString: string;
isOllama: boolean = false): string;
var
JSON: TJSONObject;
dataArray: TJSONArray;
JSONValue: TJSONValue;
JsonParts: TStringList;
I: Integer;
CleanedJson: string;
JsonObj: TJSONObject;
ResponseStr, FullResponse: string;
begin
Result := 'Ïðîèçîøëà êàêàÿ òî îøèáêà, ïîïðîáóéòå ñïðàøèâàòü ïî î÷åðåäè!';
if isOllama then
begin
JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject;
try
if Assigned(JSON) then
begin
JSONValue := TJSONObject(JSON);
if JSONValue.TryGetValue('response', JSONValue) then
Result := JSONValue.Value;
end;
finally
JSON.Free;
end;
end
else
begin
JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject;
try
if Assigned(JSON) then
begin
if JSON.TryGetValue('messages', JSONValue) then
begin
dataArray := JSONValue as TJSONArray;
if Assigned(dataArray) and (dataArray.Count > 0) then
Result := dataArray.Items[0].GetValue<string>('content');
end;
end;
finally
JSON.Free;
end;
end;
end;
function TChatAPI.GetGPTRequest(url: string; model: string; q: string;
isOllama: boolean = false): string;
var
params: TStringStream;
r: string;
begin
q := StringReplace(q, '"', '''', [rfReplaceAll]);
if isOllama then
params := TStringStream.Create('{ "model": "' + model + '", "prompt": "' +
FPrefix + q + '", "stream": false }', TEncoding.UTF8)
else
params := TStringStream.Create('{ "model": "' + model +
'", "messages": [{ "role": "user", "content": "' + FPrefix + q +
'" }], "stream": false }', CP_UTF8);
try
r := CreateHTTPRequest(url, params, isOllama);
finally
// params.Free;
end;
Result := r;
end;
end.
+410
View File
@@ -0,0 +1,410 @@
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 := '';
http := TIdHTTP.Create(nil);
try
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;
end;
except
on E: Exception do
begin
toLog(2,'GetCustom',e.Message);
Result := '';
end;
end;
http.Free;
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.
+132
View File
@@ -0,0 +1,132 @@
unit uGigaChat;
interface
uses
uChatAPI, SysUtils, IdHTTP, System.JSON, IdSSLOpenSSL, IdGlobal, classes;
type
TGigaChat = class(TChatAPI)
private
ClientID: string;
AutorizationCode: string;
function getAPIKey: string;
function GetTokenFromJson(jsonString: string): string;
protected
function GetOtvetFromJson(jsonString: string; isOllama:boolean = false): string; override;
public
constructor Create(Sender: TObject; aClientID: string; aAutorizationCode: string; aprefix: string = ''); reintroduce;
end;
implementation
{ TGigaChat }
constructor TGigaChat.Create(Sender: TObject; aClientID: string; aAutorizationCode: string; aprefix: string = '');
var AT:string;
begin
ClientID := aClientID;
AutorizationCode:=aAutorizationCode;
AT:= getAPIKey;
inherited Create(Sender, at, aprefix);
// Äîïîëíèòåëüíàÿ èíèöèàëèçàöèÿ, åñëè íåîáõîäèìî
end;
function TGigaChat.getAPIKey: string;
const
url = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth';
var
params: TStringStream;
http: TIdHTTP;
ssl: TIdSSLIOHandlerSocketOpenSSL;
begin
http := TIdHTTP.Create(nil);
ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
try
http.IOHandler := ssl;
ssl.SSLOptions.method := sslvSSLv23;
http.Request.UserAgent :=
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
http.Request.CustomHeaders.Clear;
http.Request.CustomHeaders.Add
('Content-Type: application/x-www-form-urlencoded');
http.Request.CustomHeaders.Add('Accept: application/json');
http.Request.CustomHeaders.Add('RqUID: ' + ClientID);
http.Request.CustomHeaders.Add('Authorization: Basic ' + AutorizationCode);
params := TStringStream.Create(' scope=GIGACHAT_API_PERS');
result := GetTokenFromJson(http.Post(url, params));
finally
params.Free;
http.Free;
ssl.Free;
end;
end;
function TGigaChat.GetOtvetFromJson(jsonString: string; isOllama:boolean = false): string;
var
JSON: TJSONObject;
choicesArray: TJSONArray;
choiceObject, messageObject: TJSONObject;
JSONValue: TJSONValue;
begin
Result := 'Ïðîèçîøëà êàêàÿ-òî îøèáêà, ïîïðîáóéòå ñïðàøèâàòü ïî î÷åðåäè!';
JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject;
try
if Assigned(JSON) then
begin
// Ïðîâåðÿåì íàëè÷èå êëþ÷à "choices"
if JSON.TryGetValue('choices', JSONValue) then
begin
choicesArray := JSONValue as TJSONArray;
if Assigned(choicesArray) and (choicesArray.Count > 0) then
begin
// Ïîëó÷àåì ïåðâûé ýëåìåíò ìàññèâà "choices"
choiceObject := choicesArray.Items[0] as TJSONObject;
if Assigned(choiceObject) then
begin
// Ïðîâåðÿåì íàëè÷èå êëþ÷à "message" â ïåðâîì ýëåìåíòå "choices"
if choiceObject.TryGetValue('message', JSONValue) then
begin
messageObject := JSONValue as TJSONObject;
if Assigned(messageObject) then
begin
// Èçâëåêàåì çíà÷åíèå "content" èç îáúåêòà "message"
Result := messageObject.GetValue<string>('content');
end;
end;
end;
end;
end;
end;
finally
JSON.Free;
end;
end;
function TGigaChat.GetTokenFromJson(jsonString: string): string;
var
JSON: TJSONObject;
dataArray: TJSONString;
begin
JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject;
try
if Assigned(JSON) then
begin
if pos('access_token', jsonString) <> 0 then
begin
dataArray := JSON.GetValue('access_token') as TJSONString;
if Assigned(dataArray) then
Result := dataArray.GetValue<string>();
end
else
Result := '';
end;
finally
JSON.Free;
end;
end;
end.
+212
View File
@@ -0,0 +1,212 @@
unit uKandinskyAPI;
interface
uses
System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient,
System.Net.URLClient, System.NetConsts, StrUtils, System.Net.Mime,
System.NetEncoding, System.Threading;
type
TGenerationDoneEvent = procedure(Sender: TObject; const FileName: string) of object;
TStatusUpdateEvent = procedure(Sender: TObject; const Message: string) of object;
TErrorEvent = procedure(Sender: TObject; const ErrorMessage: string) of object;
TFusionBrainAPI = class(TComponent)
private
FBaseURL: string;
FApiKey: string;
FSecretKey: string;
FClient: THTTPClient;
FOnGenerationDone: TGenerationDoneEvent;
FOnStatusUpdate: TStatusUpdateEvent;
FOnError: TErrorEvent;
procedure DoStatusUpdate(const AMessage: string);
procedure DoGenerationDone(const AFileName: string);
procedure DoError(const AErrorMessage: string);
function GetAuthHeaders: TNetHeaders;
function GetPipeline: string;
function Generate(const Prompt, PipelineId: string): string;
function CheckGeneration(const RequestId: string): TArray<string>;
procedure SaveBase64Image(const Base64Str, FileName: string);
public
constructor Create(AOwner: TComponent; aKey:string; aSecret:string);
destructor Destroy; override;
procedure StartGeneration(const APrompt: string);
property OnGenerationDone: TGenerationDoneEvent read FOnGenerationDone write FOnGenerationDone;
property OnStatusUpdate: TStatusUpdateEvent read FOnStatusUpdate write FOnStatusUpdate;
property OnError: TErrorEvent read FOnError write FOnError;
end;
implementation
uses ugeneral;
constructor TFusionBrainAPI.Create(AOwner: TComponent; aKey:string; aSecret:string);
begin
inherited Create(AOwner);
FClient := THTTPClient.Create;
FBaseURL := 'https://api-key.fusionbrain.ai/';
FApiKey :=aKey;
// FApiKey := '28C9C30489D635732FB04AA6B85F0671';
FSecretKey := aSecret;
// FSecretKey := '805CB624C052202A05E3F40C0582045A';
end;
destructor TFusionBrainAPI.Destroy;
begin
FClient.Free;
inherited;
end;
procedure TFusionBrainAPI.StartGeneration(const APrompt: string);
begin
TTask.Run(procedure
var
PipelineID, UUID, FileName: string;
Links: TArray<string>;
begin
try
TThread.Queue(nil, procedure begin DoStatusUpdate('Ïîëó÷åíèå êîíâåéåðà...'); end);
PipelineID := GetPipeline;
TThread.Queue(nil, procedure begin DoStatusUpdate('Ãåíåðàöèÿ èçîáðàæåíèÿ...'); end);
UUID := Generate(APrompt, PipelineID);
TThread.Queue(nil, procedure begin DoStatusUpdate('Ïðîâåðêà ñòàòóñà...'); end);
Links := CheckGeneration(UUID);
FileName := myConst.AppDataPath + 'imgs\kandinsky_' + FormatDateTime('yyyymmddhhnnss', Now) + '.jpg';
SaveBase64Image(Links[0], FileName);
TThread.Queue(nil, procedure begin DoGenerationDone(FileName); end);
except
on E: Exception do
TThread.Queue(nil, procedure begin DoError(E.Message); end);
end;
end);
end;
function TFusionBrainAPI.GetAuthHeaders: TNetHeaders;
begin
SetLength(Result, 2);
Result[0] := TNetHeader.Create('X-Key', 'Key ' + FApiKey);
Result[1] := TNetHeader.Create('X-Secret', 'Secret ' + FSecretKey);
end;
function TFusionBrainAPI.GetPipeline: string;
var
Response: IHTTPResponse;
Json: TJSONArray;
begin
Response := FClient.Get(FBaseURL + 'key/api/v1/pipelines', nil, GetAuthHeaders);
Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONArray;
try
Result := Json.Items[0].GetValue<string>('id');
finally
Json.Free;
end;
end;
function TFusionBrainAPI.Generate(const Prompt, PipelineId: string): string;
var
Params, Root: TJSONObject;
Multipart: TMultipartFormData;
Response: IHTTPResponse;
Json: TJSONObject;
begin
Root := TJSONObject.Create;
try
Params := TJSONObject.Create;
Params.AddPair('query', Prompt);
Root.AddPair('type', 'GENERATE');
Root.AddPair('numImages', TJSONNumber.Create(1));
Root.AddPair('width', TJSONNumber.Create(512));
Root.AddPair('height', TJSONNumber.Create(512));
Root.AddPair('generateParams', Params);
Multipart := TMultipartFormData.Create;
try
Multipart.AddField('pipeline_id', PipelineId);
Multipart.AddField('params', Root.ToString, 'application/json');
Response := FClient.Post(FBaseURL + 'key/api/v1/pipeline/run', Multipart, nil, GetAuthHeaders);
Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
try
Result := Json.GetValue<string>('uuid');
finally
Json.Free;
end;
finally
Multipart.Free;
end;
finally
Root.Free;
end;
end;
function TFusionBrainAPI.CheckGeneration(const RequestId: string): TArray<string>;
var
Response: IHTTPResponse;
Json, ResultObj: TJSONObject;
Files: TJSONArray;
i: Integer;
begin
repeat
Sleep(5000);
Response := FClient.Get(FBaseURL + 'key/api/v1/pipeline/status/' + RequestId, nil, GetAuthHeaders);
Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
try
if Json.GetValue<string>('status') = 'DONE' then
begin
ResultObj := Json.GetValue<TJSONObject>('result');
Files := ResultObj.GetValue<TJSONArray>('files');
SetLength(Result, Files.Count);
for i := 0 to Files.Count - 1 do
Result[i] := Files.Items[i].Value;
Exit;
end;
finally
Json.Free;
end;
until False;
end;
procedure TFusionBrainAPI.SaveBase64Image(const Base64Str, FileName: string);
var
DecodedStream: TMemoryStream;
InputStr: TStringStream;
begin
DecodedStream := TMemoryStream.Create;
InputStr := TStringStream.Create(Base64Str);
try
TNetEncoding.Base64.Decode(InputStr, DecodedStream);
DecodedStream.SaveToFile(FileName);
finally
DecodedStream.Free;
InputStr.Free;
end;
end;
procedure TFusionBrainAPI.DoStatusUpdate(const AMessage: string);
begin
if Assigned(FOnStatusUpdate) then
FOnStatusUpdate(Self, AMessage);
end;
procedure TFusionBrainAPI.DoGenerationDone(const AFileName: string);
begin
if Assigned(FOnGenerationDone) then
FOnGenerationDone(Self, AFileName);
end;
procedure TFusionBrainAPI.DoError(const AErrorMessage: string);
begin
if Assigned(FOnError) then
FOnError(Self, AErrorMessage);
end;
end.
+1096
View File
File diff suppressed because it is too large Load Diff
+660
View File
@@ -0,0 +1,660 @@
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 SafeGetStr(Parent: TJSONObject; const Name: string): string;
var
V: TJSONValue;
begin
V := Parent.GetValue(Name);
if Assigned(V) then
Result := V.Value
else
Result := '';
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;
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
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 md.subscription_type = 'channel.channel_points_custom_reward_redemption.add' then
if Assigned(FOnGetCustomReward) then
FOnGetCustomReward(ParseCustomRewardEvent(AText));
if md.subscription_type = 'channel.follow' then
if Assigned(FOnFollow) then
FOnFollow(ParseFollowEvent(AText));
// Òóò àíàëîãè÷íî ìîæíî âûçûâàòü ParseSubEvent, ParseGiftEvent, ParseRaidEvent
end
else if md.message_type = 'session_keepalive' then
toLog(3, 'EventMSG', 'Ïîëó÷åí keepalive');
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 := SafeGetStr(Metadata, 'message_id');
Result.message_type := SafeGetStr(Metadata, 'message_type');
Result.message_timestamp := SafeGetStr(Metadata, 'message_timestamp');
Result.subscription_type := SafeGetStr(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 := SafeGetStr(Session, 'id');
Result.Payload.session.status := SafeGetStr(Session, 'status');
Result.Payload.session.connected_at := SafeGetStr(Session, 'connected_at');
Result.Payload.session.keepalive_timeout_seconds := SafeGetInt(Session, 'keepalive_timeout_seconds');
Result.Payload.session.reconnect_url := SafeGetStr(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 := SafeGetStr(Subscription, 'id');
subscription_type := SafeGetStr(Subscription, 'type');
version := SafeGetStr(Subscription, 'version');
status := SafeGetStr(Subscription, 'status');
cost := SafeGetInt(Subscription, 'cost');
created_at := SafeGetStr(Subscription, 'created_at');
mCondition := SafeGetObj(Subscription, 'condition');
condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id');
condition.reward_id := SafeGetStr(mCondition, 'reward_id');
mTransport := SafeGetObj(Subscription, 'transport');
transport.method := SafeGetStr(mTransport, 'method');
end;
Event := SafeGetObj(Payload, 'event');
with Result.Event do
begin
id := SafeGetStr(Event, 'id');
broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id');
broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login');
broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name');
user_id := SafeGetStr(Event, 'user_id');
user_login := SafeGetStr(Event, 'user_login');
user_name := SafeGetStr(Event, 'user_name');
user_input := SafeGetStr(Event, 'user_input');
mReward := SafeGetObj(Event, 'reward');
revard.id := SafeGetStr(mReward, 'id');
revard.title := SafeGetStr(mReward, 'title');
revard.cost := SafeGetInt(mReward, 'cost');
revard.prompt := SafeGetStr(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 := SafeGetStr(Subscription, 'id');
subscription_type := SafeGetStr(Subscription, 'type');
version := SafeGetStr(Subscription, 'version');
status := SafeGetStr(Subscription, 'status');
cost := SafeGetInt(Subscription, 'cost');
created_at := SafeGetStr(Subscription, 'created_at');
mCondition := SafeGetObj(Subscription, 'condition');
condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id');
mTransport := SafeGetObj(Subscription, 'transport');
transport.method := SafeGetStr(mTransport, 'method');
end;
Event := SafeGetObj(Payload, 'event');
with Result.Event do
begin
broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id');
broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login');
broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name');
user_id := SafeGetStr(Event, 'user_id');
user_login := SafeGetStr(Event, 'user_login');
user_name := SafeGetStr(Event, 'user_name');
followed_at := SafeGetStr(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 := SafeGetStr(Subscription, 'id');
subscription_type := SafeGetStr(Subscription, 'type');
version := SafeGetStr(Subscription, 'version');
status := SafeGetStr(Subscription, 'status');
cost := SafeGetInt(Subscription, 'cost');
created_at := SafeGetStr(Subscription, 'created_at');
mCondition := SafeGetObj(Subscription, 'condition');
condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id');
mTransport := SafeGetObj(Subscription, 'transport');
transport.method := SafeGetStr(mTransport, 'method');
end;
Event := SafeGetObj(Payload, 'event');
with Result.Event do
begin
broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id');
broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login');
broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name');
user_id := SafeGetStr(Event, 'user_id');
user_login := SafeGetStr(Event, 'user_login');
user_name := SafeGetStr(Event, 'user_name');
total := SafeGetInt(Event, 'total');
tier := SafeGetStr(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 := SafeGetStr(Subscription, 'id');
subscription_type := SafeGetStr(Subscription, 'type');
version := SafeGetStr(Subscription, 'version');
status := SafeGetStr(Subscription, 'status');
cost := SafeGetInt(Subscription, 'cost');
created_at := SafeGetStr(Subscription, 'created_at');
mCondition := SafeGetObj(Subscription, 'condition');
condition.broadcaster_user_id := SafeGetStr(mCondition, 'to_broadcaster_user_id');
mTransport := SafeGetObj(Subscription, 'transport');
transport.method := SafeGetStr(mTransport, 'method');
end;
Event := SafeGetObj(Payload, 'event');
with Result.Event do
begin
from_broadcaster_user_id := SafeGetStr(Event, 'from_broadcaster_user_id');
from_broadcaster_user_login := SafeGetStr(Event, 'from_broadcaster_user_login');
from_broadcaster_user_name := SafeGetStr(Event, 'from_broadcaster_user_name');
to_broadcaster_user_id := SafeGetStr(Event, 'to_broadcaster_user_id');
to_broadcaster_user_login := SafeGetStr(Event, 'to_broadcaster_user_login');
to_broadcaster_user_name := SafeGetStr(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 := SafeGetStr(Subscription, 'id');
subscription_type := SafeGetStr(Subscription, 'type');
version := SafeGetStr(Subscription, 'version');
status := SafeGetStr(Subscription, 'status');
cost := SafeGetInt(Subscription, 'cost');
created_at := SafeGetStr(Subscription, 'created_at');
mCondition := SafeGetObj(Subscription, 'condition');
condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id');
mTransport := SafeGetObj(Subscription, 'transport');
transport.method := SafeGetStr(mTransport, 'method');
end;
Event := SafeGetObj(Payload, 'event');
with Result.Event do
begin
broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id');
broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login');
broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name');
user_id := SafeGetStr(Event, 'user_id');
user_login := SafeGetStr(Event, 'user_login');
user_name := SafeGetStr(Event, 'user_name');
tier := SafeGetStr(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);
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;
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.
+364
View File
@@ -0,0 +1,364 @@
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; // 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;
//
LSpacePos := Pos(' ', s);
if LSpacePos = 0 then
Exit;
LParamStr := Copy(s, 1, LSpacePos - 1);
LRestStr := Copy(s, LSpacePos + 1, Length(s) - LSpacePos);
//
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 := '';
//
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 := '';
//
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.
+289
View File
@@ -0,0 +1,289 @@
unit uTWAuth;
interface
uses
System.SysUtils, System.Classes, IdContext, IdCustomHTTPServer, IdHTTPServer,
IdComponent, ShellAPI, System.Threading, Windows;
type
TmyEvent = procedure(txt: string) of object;
type
TTTWAuth = class
FmyEvent: TmyEvent;
FURL: string;
private
FHTTPServer: TIdHTTPServer;
procedure HandleRequest(ASender: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure HandleRootRequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
procedure HandleRedirectRequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
procedure HandleDARequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
procedure OnStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
public
constructor Create;
destructor Destroy; override;
procedure StartServer(aURL: string);
procedure StopServer;
property OnToken: TmyEvent read FmyEvent write FmyEvent;
property OnError: TmyEvent read FmyEvent write FmyEvent;
end;
implementation
{ TTTWAuth }
constructor TTTWAuth.Create;
begin
inherited Create;
FHTTPServer := TIdHTTPServer.Create(nil);
FHTTPServer.OnCommandGet := HandleRequest;
FHTTPServer.OnStatus := OnStatus;
// Íå âêëþ÷àåì Active çäåñü
end;
destructor TTTWAuth.Destroy;
begin
try
if Assigned(FHTTPServer) then
begin
try
if FHTTPServer.Active then
FHTTPServer.Active := False;
except
end;
FreeAndNil(FHTTPServer);
end;
except
end;
inherited;
end;
procedure TTTWAuth.StartServer(aURL: string);
begin
// Çàùèòà îò ïîâòîðíîãî çàïóñêà
if Assigned(FHTTPServer) and FHTTPServer.Active then
Exit;
// Î÷èñòèì ñòàðûå áèíäèíãè, ÷òîáû íå íàêàïëèâàòü èõ
FHTTPServer.Bindings.Clear;
FHTTPServer.DefaultPort := 80;
// Äîáàâëÿåì áèíäèíã ÿâíî
FHTTPServer.Bindings.Add.SetBinding('127.0.0.1', 80);
FURL := aURL;
FHTTPServer.Active := True;
if FURL <> '' then
ShellExecute(0, 'open', PWideChar(FURL), nil, nil, SW_SHOWNORMAL);
end;
procedure TTTWAuth.StopServer;
begin
if Assigned(FHTTPServer) then
begin
try
FHTTPServer.Active := False;
except
end;
try
FHTTPServer.Bindings.Clear;
except
end;
end;
end;
procedure TTTWAuth.HandleRequest(ASender: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '/' then
HandleRootRequest(ARequestInfo, AResponseInfo)
else if ARequestInfo.Document = '/redirect' then
HandleRedirectRequest(ARequestInfo, AResponseInfo)
else if ARequestInfo.Document = '/da' then
HandleDARequest(ARequestInfo, AResponseInfo)
else
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := 'Not Found';
end;
end;
procedure TTTWAuth.HandleRootRequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ContentText := '<!DOCTYPE html>' + sLineBreak + '<html>' +
sLineBreak + '<head>' + sLineBreak +
' <title>Redirecting...</title>' + sLineBreak + '</head>' + sLineBreak +
'<body>' + sLineBreak + ' <p>ïîëó÷àþ òîêåí:</p>' + sLineBreak + '<script>' +
sLineBreak + 'var paragraph = window.location.href;' + sLineBreak +
'var urrl = paragraph.replace(''localhost/'',''localhost/redirect'');' +
sLineBreak + 'urrl = urrl.replace(''#'',''?'');' + sLineBreak +
'console.log(urrl);' + sLineBreak + 'window.location.href =urrl;' +
sLineBreak + ' </script>' + sLineBreak + '</body>' + sLineBreak + '</html>';
end;
procedure TTTWAuth.OnStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
// Ìîæíî ëîãèðîâàòü ñòàòóñ, íî íå îáÿçàòåëüíî
end;
procedure TTTWAuth.HandleDARequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ContentText := '<!DOCTYPE html>' + sLineBreak + '<html>' +
sLineBreak + '<head>' + sLineBreak +
' <title>Redirecting...</title>' + sLineBreak + '</head>' + sLineBreak +
'<body>' + sLineBreak + ' <p>ïîëó÷àþ êîä</p>' + sLineBreak + '<script>' +
sLineBreak + 'var paragraph = window.location.href;' + sLineBreak +
'var urrl = paragraph.replace(''localhost/da'',''localhost/redirect'');' +
sLineBreak + 'urrl = urrl.replace(''#'',''?'');' + sLineBreak +
'console.log(urrl);' + sLineBreak + 'window.location.href =urrl;' +
sLineBreak + ' </script>' + sLineBreak + '</body>' + sLineBreak + '</html>';
end;
procedure TTTWAuth.HandleRedirectRequest(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
var
i: Integer;
AccessToken: string;
LTokenCopy: string;
begin
// Åñëè ïîëó÷åí access_token
if Pos('access_token=', ARequestInfo.Params.Text) > 0 then
begin
for i := 0 to ARequestInfo.Params.Count - 1 do
if Pos('access_token', ARequestInfo.Params[i]) > 0 then
AccessToken := ARequestInfo.Params[i];
AccessToken := StringReplace(AccessToken, 'access_token=', '', [rfReplaceAll]);
AResponseInfo.ContentText := '<!DOCTYPE html>' + sLineBreak + '<html>' +
sLineBreak + '<head>' + sLineBreak +
' <title>Done...</title>' + sLineBreak + '</head>' + sLineBreak +
'<body>' + sLineBreak + 'Ýòó ñòðàíèöó ìîæíî çàêðûòü' + sLineBreak +
'</body>' + sLineBreak + '</html>';
AResponseInfo.WriteContent;
// Êîïèðóåì òîêåí, ÷òîáû êîððåêòíî ïåðåäàòü â main thread
LTokenCopy := AccessToken;
// Âûçûâàåì OnToken â main thread
if Assigned(FmyEvent) then
TThread.Queue(nil,
procedure
begin
try
FmyEvent(LTokenCopy);
except
end;
end);
// Îñòàíîâèì ñåðâåð è çàïëàíèðóåì î÷èñòêó îáúåêòà â main thread
try
StopServer;
except
end;
TThread.Queue(nil,
procedure
begin
try
Free;
except
end;
end);
Exit;
end;
// Åñëè åñòü error_description
if Pos('error_description=', ARequestInfo.Params.Text) > 0 then
begin
for i := 0 to ARequestInfo.Params.Count - 1 do
if Pos('error_description', ARequestInfo.Params[i]) > 0 then
AccessToken := ARequestInfo.Params[i];
AccessToken := StringReplace(AccessToken, 'error_description=', '', [rfReplaceAll]);
AResponseInfo.ContentText := '<!DOCTYPE html>' + sLineBreak + '<html>' +
sLineBreak + '<head>' + sLineBreak +
' <title>ERROR...</title>' + sLineBreak + '</head>' + sLineBreak +
'<body>' + sLineBreak + AccessToken + sLineBreak + '</body>' + sLineBreak + '</html>';
AResponseInfo.WriteContent;
LTokenCopy := AccessToken;
if Assigned(FmyEvent) then
TThread.Queue(nil,
procedure
begin
try
FmyEvent(LTokenCopy);
except
end;
end);
try
StopServer;
except
end;
TThread.Queue(nil,
procedure
begin
try
Free;
except
end;
end);
Exit;
end;
// Åñëè ïîëó÷åí code=
if Pos('code=', ARequestInfo.Params.Text) > 0 then
begin
for i := 0 to ARequestInfo.Params.Count - 1 do
if Pos('code', ARequestInfo.Params[i]) > 0 then
AccessToken := ARequestInfo.Params[i];
AccessToken := StringReplace(AccessToken, 'code=', '', [rfReplaceAll]);
AResponseInfo.ContentText := '<!DOCTYPE html>' + sLineBreak + '<html>' +
sLineBreak + '<head>' + sLineBreak +
' <title>Done...</title>' + sLineBreak + '</head>' + sLineBreak +
'<body>' + sLineBreak + 'Ýòó ñòðàíèöó ìîæíî çàêðûòü' + sLineBreak +
'</body>' + sLineBreak + '</html>';
AResponseInfo.WriteContent;
LTokenCopy := AccessToken;
if Assigned(FmyEvent) then
TThread.Queue(nil,
procedure
begin
try
FmyEvent(LTokenCopy);
except
end;
end);
try
StopServer;
except
end;
TThread.Queue(nil,
procedure
begin
try
Free;
except
end;
end);
Exit;
end;
// Ïî óìîë÷àíèþ — 404
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := 'Not Found';
AResponseInfo.WriteContent;
end;
end.
+228
View File
@@ -0,0 +1,228 @@
unit uWSDA;
interface
uses
Classes, SysUtils, System.JSON, ipwwsclient, StrUtils, uAPIDA;
type
TOnDonateEvent = procedure(aNick, aMessage, aSum: string) of object;
TOnStatusEvent = procedure(AStatusText: string; AStatusCode: integer) of object;
TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object;
TWSClient = class(TObject)
private
FWS: TipwWSClient;
FAPIClient: TAPIClient;
FOnDonate: TOnDonateEvent;
FOnStatus: TOnStatusEvent;
FOnLog: TOnLog;
FWsstoken: string;
FWSID: string;
procedure DataIn(Sender: TObject; DataFormat: integer; const Text: string;
const TextB: TBytes; EOM, EOL: Boolean);
procedure ConnectionStatus(Sender: TObject; const ConnectionEvent: string;
StatusCode: integer; const Description: string);
procedure Error(Sender: TObject; ErrorCode: integer; const Description: string);
function ExtractValue(const T_, Text, _T: string): string;
procedure HandleIncomingData(const Data: string);
procedure toLog(aLevel: integer; aMethod: string; aMessage: string);
public
constructor Create;
destructor Destroy; override;
procedure Connect(const WSSURL: string);
procedure Disconnect;
procedure Send(const Data: string);
property OnDonate: TOnDonateEvent read FOnDonate write FOnDonate;
property OnStatus: TOnStatusEvent read FOnStatus write FOnStatus;
property OnLog: TOnLog read FOnLog write FOnLog;
property Wsstoken: string read FWsstoken write FWsstoken;
property WSID: string read FWSID write FWSID;
property APIClient: TAPIClient read FAPIClient write FAPIClient;
end;
implementation
constructor TWSClient.Create;
begin
inherited Create;
FWS := TipwWSClient.Create(nil);
FWS.OnDataIn := DataIn;
FWS.OnConnectionStatus := ConnectionStatus;
FWS.OnError := Error;
FAPIClient := nil;
FOnDonate := nil;
FOnStatus := nil;
FOnLog := nil;
end;
destructor TWSClient.Destroy;
begin
try
if Assigned(FWS) then
begin
try
// î÷èñòèì îáðàáîò÷èêè ÷òîáû íå áûëî îáðàòíûõ âûçîâîâ â ìîìåíò îñâîáîæäåíèÿ
FWS.OnDataIn := nil;
FWS.OnConnectionStatus := nil;
FWS.OnError := nil;
try
FWS.Disconnect;
except
// èãíîðèðóåì îøèáêè ïðè îòêëþ÷åíèè
end;
finally
FreeAndNil(FWS);
end;
end;
except
// íè÷åãî íå äåëàåì — çàùèòà îò èñêëþ÷åíèé â äåñòðóêòîðå
end;
inherited;
end;
procedure TWSClient.Disconnect;
begin
if Assigned(FWS) then
begin
try
FWS.Disconnect;
except
// èãíîðèðóåì
end;
end;
end;
procedure TWSClient.Connect(const WSSURL: string);
begin
if Assigned(FWS) then
begin
try
FWS.ConnectTo(WSSURL);
except
// ëîãèðîâàòü ïðè íåîáõîäèìîñòè
toLog(2, 'Connect', 'Exception on Connect');
end;
end;
end;
procedure TWSClient.Send(const Data: string);
begin
if Assigned(FWS) then
begin
try
FWS.SendText(Data);
except
toLog(2, 'Send', 'Exception on Send');
end;
end;
end;
procedure TWSClient.toLog(aLevel: integer; aMethod: string; aMessage: string);
begin
if Assigned(FOnLog) then
FOnLog('uWSDA', aMethod, aMessage, aLevel);
end;
procedure TWSClient.DataIn(Sender: TObject; DataFormat: integer;
const Text: string; const TextB: TBytes; EOM, EOL: Boolean);
begin
try
HandleIncomingData(Text);
except
on E: Exception do
toLog(2, 'DataIn', E.Message);
end;
//FWS.Ping; // åñëè íóæíî
end;
procedure TWSClient.ConnectionStatus(Sender: TObject; const ConnectionEvent: string;
StatusCode: integer; const Description: string);
begin
if Assigned(FOnStatus) then
FOnStatus(ConnectionEvent, StatusCode);
end;
procedure TWSClient.Error(Sender: TObject; ErrorCode: integer; const Description: string);
begin
toLog(2, 'Error', '[' + IntToStr(ErrorCode) + '] ' + Description);
end;
function TWSClient.ExtractValue(const T_, Text, _T: string): string;
var
StartPos, EndPos: integer;
begin
StartPos := Pos(T_, Text);
if StartPos = 0 then
Exit('');
StartPos := StartPos + Length(T_);
EndPos := PosEx(_T, Text, StartPos);
if EndPos = 0 then
Exit('');
Result := Copy(Text, StartPos, EndPos - StartPos);
end;
procedure TWSClient.HandleIncomingData(const Data: string);
var
JSON: TJSONObject;
DataObj: TJSONObject;
ChannelArray: TJSONArray;
jo: TJSONObject;
wsstoken2: string;
begin
toLog(3, 'HandleIncomingData', Data);
// Îáðàáîòêà ðåãèñòðàöèè êëèåíòà
if Pos('"result":{"client":"', Data) > 0 then
begin
FWsstoken := ExtractValue('"result":{"client":"', Data, '",');
toLog(3, 'HandleIncomingData', 'Êëèåíò çàðåãèñòðèðîâàí');
if Assigned(FAPIClient) then
begin
try
jo := FAPIClient.SubscribeToChannel(FWSID, FWsstoken);
except
jo := nil;
end;
if Assigned(jo) then
try
toLog(3, 'HandleIncomingData', 'Êëèåíò ïîäïèñàí');
ChannelArray := jo.Values['channels'] as TJSONArray;
if Assigned(ChannelArray) and (ChannelArray.Count > 0) then
begin
wsstoken2 := ChannelArray.Items[0].GetValue<string>('token');
toLog(3, 'HandleIncomingData', 'Ïîäïèñêà íà êàíàë ñ òîêåíîì: ' + wsstoken2);
try
FWS.SendText('{"params": {"channel": "$alerts:donation_' + FWSID + '","token": "' + wsstoken2 + '"},"method": 1,"id": 2 }');
except
toLog(2, 'HandleIncomingData', 'SendText failed');
end;
end;
finally
jo.Free;
end;
end;
end;
// Îáðàáîòêà äîíàòîâ
if Pos('"name":"Donations"', Data) > 0 then
begin
toLog(3, 'HandleIncomingData', 'Íîâûé Äîíàò');
JSON := nil;
try
JSON := TJSONObject.ParseJSONValue(Data) as TJSONObject;
if Assigned(JSON) then
begin
DataObj := JSON.GetValue<TJSONObject>('result').GetValue<TJSONObject>('data').GetValue<TJSONObject>('data');
if Assigned(DataObj) and Assigned(FOnDonate) then
FOnDonate(DataObj.GetValue<string>('username'),
DataObj.GetValue<string>('message'),
DataObj.GetValue<string>('amount'));
end;
finally
JSON.Free;
end;
end;
end;
end.
+227
View File
@@ -0,0 +1,227 @@
unit uWebServerKandinsky;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, System.NetEncoding,IdContext, IdCustomHTTPServer, IdHTTPServer, IdGlobal,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Edit,
FMX.Controls.Presentation, FMX.StdCtrls, uKandinskyAPI, FMX.Memo.Types, json,
FMX.ScrollBox, FMX.Memo, System.IOUtils, System.SyncObjs,System.DateUtils;
type
TKandinsky_Web = class(TObject)
IdHTTPServer1: TIdHTTPServer;
procedure IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
private
FCriticalSection: TCriticalSection;
FCurrentImage: string;
FImageTime: TDateTime;
FCurrentText: string;
ka:TFusionBrainAPI;
function GenerateHTML: string;
function GenerateJSON: string;
procedure CleanupOldMessages;
procedure GenerationDone(Sender: TObject; const FileName: string);
procedure GenerationError(Sender: TObject; const ErrorMessage: string);
procedure GenerationUpdate(Sender: TObject; const Message: string);
public
constructor Create(aKey:string; aSecret:string);
destructor Destroy;
procedure generate(prompt:string; aNick:string);
procedure ActiveServer(aEn: boolean);
end;
implementation
{ TKandinsky_Web }
procedure TKandinsky_Web.ActiveServer(aEn: boolean);
begin
IdHTTPServer1.Active :=aEn;
end;
procedure TKandinsky_Web.CleanupOldMessages;
begin
if FileExists(FCurrentImage) then
begin
DeleteFile(FCurrentImage);
end;
end;
constructor TKandinsky_Web.Create(aKey:string; aSecret:string);
begin
IdHTTPServer1 := TIdHTTPServer.Create;
IdHTTPServer1.DefaultPort := 8087;
IdHTTPServer1.OnCommandGet := IdHTTPServer1CommandGet;
ka:=TFusionBrainAPI.Create(nil,aKey, aSecret);
ka.OnGenerationDone := GenerationDone;
ka.OnStatusUpdate:=GenerationUpdate;
ka.OnError:=GenerationError;
FCriticalSection:=TCriticalSection.Create;
//flog.toLog(0,'uWebServerKandinsky','Create','Âåá ñåðâåð çàïóùåí');
end;
destructor TKandinsky_Web.Destroy;
begin
IdHTTPServer1.Active := False;
FCriticalSection.Free;
CleanupOldMessages;
end;
procedure TKandinsky_Web.generate(prompt: string; aNick:string);
begin
//flog.toLog(0,'uWebServerKandinsky','generate','Íîâûé çàïðîñ íà ãåíåðàöèþ');
FCriticalSection.Enter;
try
FCurrentText := aNick;
finally
FCriticalSection.Leave;
end;
ka.StartGeneration(prompt);
//flog.toLog(0,'uWebServerKandinsky','generate','Çàïðîñ íà ãåíåðàöèþ îòïðàâëåí');
end;
function TKandinsky_Web.GenerateHTML: string;
begin
Result := '<!DOCTYPE html>' +
'<html><head>' +
'<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">' +
'<meta http-equiv="Pragma" content="no-cache">' +
'<meta http-equiv="Expires" content="0">' +
'<style>' +
'body { background: #00ff00; }' +
'#current-image { max-width: 100%; max-height: 90vh; margin: 5vh auto; display: block; }' +
'#image-text { text-align: center; font-size: 24px; margin: 10px; color: black; }' +
'.hidden { display: none !important; }' + // Äîáàâèëè êëàññ hidden
'</style></head>' +
'<body>' +
'<div id="image-container">' +
' <img id="current-image" class="hidden" src="" />' + // Íà÷àëüíîå ñîñòîÿíèå hidden
' <div id="image-text" class="hidden"></div>' + // Íà÷àëüíîå ñîñòîÿíèå hidden
'</div>' +
'<script>' +
'function updateImage() {' +
' fetch("/image-data")' +
' .then(response => response.json())' +
' .then(data => {' +
' const img = document.getElementById("current-image");' +
' const textDiv = document.getElementById("image-text");' +
' ' +
' if (data.imageUrl && data.text) {' +
' if (img.src !== data.imageUrl) {' +
' img.src = data.imageUrl;' +
' textDiv.textContent = data.text;' +
' }' +
' img.classList.remove("hidden");' +
' textDiv.classList.remove("hidden");' +
' } else {' + // Îáðàáîòêà ñëó÷àÿ êîãäà íåò èçîáðàæåíèÿ
' img.classList.add("hidden");' +
' textDiv.classList.add("hidden");' +
' img.src = "";' + // Î÷èùàåì src
' textDiv.textContent = "";' +
' }' +
' })' +
' .catch(error => console.error("Error:", error));' +
'}' +
'setInterval(updateImage, 1000);' +
'updateImage();' +
'</script>' +
'</body></html>';
end;
function TKandinsky_Web.GenerateJSON: string;
var
JSONObject: TJSONObject;
begin
JSONObject := TJSONObject.Create;
try
FCriticalSection.Enter;
try
// Èçìåíèëè óñëîâèå ïðîâåðêè âðåìåíè
if FileExists(FCurrentImage) and (SecondsBetween(Now, FImageTime) <= 5) then
begin
JSONObject.AddPair('imageUrl', '/image?' + IntToStr(DateTimeToUnix(FImageTime))); // Èñïîëüçóåì âðåìÿ ãåíåðàöèè
JSONObject.AddPair('text', FCurrentText)
end
else
begin
JSONObject.AddPair('imageUrl', '');
JSONObject.AddPair('text', '');
end;
finally
FCriticalSection.Leave;
end;
Result := JSONObject.ToString;
finally
JSONObject.Free;
end;
end;
procedure TKandinsky_Web.GenerationDone(Sender: TObject;
const FileName: string);
begin
TThread.Queue(nil, procedure
begin
FCriticalSection.Enter;
try
CleanupOldMessages;
FCurrentImage := FileName;
FImageTime := Now;
//flog.toLog(0,'uWebServerKandinsky','GenerationDone','Ôàéë êàðòèíêè ñîçäàí');
finally
FCriticalSection.Leave;
end;
end);
end;
procedure TKandinsky_Web.GenerationError(Sender: TObject;
const ErrorMessage: string);
begin
//flog.toLog(2,'uWebServerKandinsky','GenerationError',ErrorMessage);
end;
procedure TKandinsky_Web.GenerationUpdate(Sender: TObject;
const Message: string);
begin
// flog.toLog(0,'uWebServerKandinsky','GenerationUpdate',Message);
end;
procedure TKandinsky_Web.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
FilePath: string;
begin
FCriticalSection.Enter;
try
if ARequestInfo.Document = '/' then
begin
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := GenerateHTML;
end
else if ARequestInfo.Document = '/image' then
begin
if FileExists(FCurrentImage) and (SecondsBetween(Now, FImageTime) <= 5) then
begin
AResponseInfo.ContentType := 'image/jpeg';
AResponseInfo.ContentStream := TFileStream.Create(FCurrentImage, fmOpenRead);
end
else
AResponseInfo.ResponseNo := 404;
end
else if ARequestInfo.Document = '/image-data' then
begin
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := GenerateJSON;
end
else
AResponseInfo.ResponseNo := 404;
finally
FCriticalSection.Leave;
end;
end;
end.