сделал оповещения о событиях

This commit is contained in:
PC1\PTyTb
2025-08-14 21:43:02 +03:00
parent bad576dd4d
commit 2335bf1656
8 changed files with 874 additions and 35 deletions
+341
View File
@@ -0,0 +1,341 @@
unit uWebServerEvents;
interface
uses
Classes, StrUtils, DateUtils, System.JSON, System.Generics.Collections,
IdBaseComponent, IdComponent, IdCustomTCPServer, IdContext,
IdCustomHTTPServer, uRecords, System.IOUtils, IdGlobalProtocols,
IdHTTPServer, System.SysUtils, SyncObjs;
type
TTTW_Events = class(TObject)
private
msgStyle: TStyleEvent;
fFontsList: TStringList;
FMessages: TList<TStyleEvent>;
FCriticalSection: TCriticalSection;
procedure IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure ProcessFileRequest(ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo; const Folder: string);
function GenerateHTML: string;
function GenerateJSON: string;
procedure CleanupOldMessages;
public
IdHTTPServer1: TIdHTTPServer;
constructor Create(FontList: TStrings; aPort:integer);
destructor Destroy; override;
procedure addMessage(newMsg: TStyleEvent);
procedure ActiveServer(aEn: boolean);
end;
implementation
uses ugeneral;
{ TTTW_Events }
constructor TTTW_Events.Create(FontList: TStrings; aPort:integer);
var
I: Integer;
begin
FCriticalSection := TCriticalSection.Create;
FMessages := TList<TStyleEvent>.Create;
fFontsList := TStringList.Create;
fFontsList.Assign(FontList);
IdHTTPServer1 := TIdHTTPServer.Create(nil);
IdHTTPServer1.DefaultPort := aPort;
IdHTTPServer1.OnCommandGet := IdHTTPServer1CommandGet;
end;
destructor TTTW_Events.Destroy;
begin
FCriticalSection.Free;
FMessages.Free;
fFontsList.Free;
IdHTTPServer1.Free;
inherited;
end;
procedure TTTW_Events.addMessage(newMsg: TStyleEvent);
begin
FCriticalSection.Enter;
try
FMessages.Add(newMsg);
CleanupOldMessages;
finally
FCriticalSection.Leave;
end;
end;
procedure TTTW_Events.CleanupOldMessages;
var
I: Integer;
TimeNow: TDateTime;
begin
TimeNow := Now;
FCriticalSection.Enter;
try
for I := FMessages.Count - 1 downto 0 do
begin
if SecondsBetween(TimeNow, FMessages[I].Timestamp) >= FMessages[I].TimeMsg then
FMessages.Delete(I);
end;
finally
FCriticalSection.Leave;
end;
end;
function TTTW_Events.GenerateHTML: string;
var
I: Integer;
s, s1: string;
begin
// Ãåíåðàöèÿ CSS äëÿ øðèôòîâ
s := 'body { background: #00FF00; }' + #13#10;
for I := 41 to fFontsList.Count - 1 do
begin
s1 := StringReplace(fFontsList[I], '.ttf', '', [rfReplaceAll]);
s := s + Format('@font-face { font-family: ''%s''; src: url(fonts/%s); }', [s1, fFontsList[I]]) + #13#10;
end;
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">' +
'<title>Twitch Messages</title>' +
'<style>' + s +
'.message { ' +
' will-change: transform, opacity;' + // Îïòèìèçàöèÿ àíèìàöèè
' backface-visibility: hidden;' +
' transform: translateZ(0);' +
' margin:5px; ' +
' border-radius:5px; ' +
' transition: opacity 1s linear; ' +
' max-width: 600px; ' +
' margin-left: auto; ' +
' margin-right: auto; ' +
'}' +
'.nick { margin: 0; padding: 2px; }' +
'.text { margin: 0; padding: 5px; }' +
'#audio-warning { ' +
' display: none; ' +
' position: fixed; ' +
' top: 10px; ' +
' right: 10px; ' +
' background: #ffcccc; ' +
' padding: 10px; ' +
' border: 1px solid red; ' +
'}' +
'</style>' +
'<script>' +
'let lastPlayedTimestamp = 0;' +
'let audioEnabled = false;' +
'let pendingMessages = [];' +
'function enableAudio() {' +
' audioEnabled = true;' +
' document.getElementById("audio-overlay").style.display = "none";' +
' processPendingMessages();' +
'}' +
'function processPendingMessages() {' +
' pendingMessages.forEach(msg => {' +
' playNotificationSound(msg);' +
' });' +
' pendingMessages = [];' +
'}' +
'function playNotificationSound(msg) {' +
' if(!msg.sound) return;' +
' const audio = new Audio(msg.sound);' +
' audio.play()' +
' .catch(error => console.log("Audio error:", error));' +
'}' +
'function fetchMessages() {' +
' fetch("/messages")' +
' .then(response => response.json())' +
' .then(data => {' +
' const container = document.getElementById("messages");' +
' container.innerHTML = "";' +
' data.forEach(msg => {' +
// Ñîõðàíÿåì ñîîáùåíèÿ äî àêòèâàöèè çâóêà
' if(msg.sound && msg.timestamp > lastPlayedTimestamp) {' +
' playNotificationSound(msg);' +
' lastPlayedTimestamp = msg.timestamp;' +
' }' +
' const div = document.createElement("div");' +
' div.className = "message";' +
' div.id = "msg-" + msg.timestamp;' +
' div.style = `' +
' background-color: ${msg.color};' +
' padding: ${msg.padding}px;' +
' border: ${msg.sizeBorder}px solid ${msg.colorBorder};' +
' text-align: center;' +
' `;' +
// Âíóòðåííèé HTML
' let content = "";' +
' if(msg.url) {' +
' content += `<img src="${msg.url}" style="max-width: 100%; height: auto;">`;' +
' }' +
' content += `' +
' <p class="nick" style="' +
' color: ${msg.titlecolor};' +
' font-family: ''${msg.titlefamily}'';' +
' font-size: ${msg.titleSize}px;">' +
' ${msg.nickname}' +
' </p>' +
' <p class="text" style="' +
' color: ${msg.contentcolor};' +
' font-family: ''${msg.contentfamily}'';' +
' font-size: ${msg.contentSize}px;">' +
' ${msg.content}' +
' </p>' +
' `;' +
' div.innerHTML = content;' +
// Àíèìàöèÿ èñ÷åçíîâåíèÿ
' setTimeout(() => {' +
' div.style.opacity = "0";' +
' setTimeout(() => div.remove(), 1000);' +
' }, (msg.duration - 1) * 1000);' +
' container.appendChild(div);' +
' });' +
' });' +
'}' +
'setInterval(fetchMessages, 500);' +
'fetchMessages();' +
'</script>' +
'</head>' +
'<body>' +
' <div id="messages"></div>' +
'</body></html>';
end;
function TTTW_Events.GenerateJSON: string;
var
JSONArray: TJSONArray;
I: Integer; S,S1:STRING;
Msg: TStyleEvent;
begin
JSONArray := TJSONArray.Create;
try
FCriticalSection.Enter;
try
CleanupOldMessages;
for I := 0 to FMessages.Count - 1 do
begin
Msg := FMessages[I];
s:=StringReplace(Msg.FontTitle.Font,'.ttf','',[rfReplaceAll]);
s1:=StringReplace(Msg.FontContext.Font,'.ttf','',[rfReplaceAll]);
JSONArray.AddElement(TJSONObject.Create
.AddPair('nickname', Msg.Title)
.AddPair('url', Msg.Url)
.AddPair('content', Msg.Context)
.AddPair('timestamp', TJSONNumber.Create(DateTimeToUnix(Msg.Timestamp)))
.AddPair('sound', Msg.SoundURL)
.AddPair('duration', Msg.TimeMsg)
.AddPair('color', Msg.BlockColor)
.AddPair('colorBorder', Msg.BorderColor)
.AddPair('sizeBorder', TJSONNumber.Create(Msg.BorderSize))
.AddPair('fontSize', TJSONNumber.Create(Msg.FontTitle.size))
.AddPair('titlecolor', Msg.FontTitle.Color)
.AddPair('titlefamily', s)
.AddPair('titleSize', TJSONNumber.Create(Msg.FontTitle.Size))
.AddPair('contentcolor', Msg.FontContext.Color)
.AddPair('contentfamily', s1)
.AddPair('contentSize', TJSONNumber.Create(Msg.FontContext.Size))
); // Ôèêñèðîâàííûé ðàçìåð òåêñòà
end;
finally
FCriticalSection.Leave;
end;
Result := JSONArray.ToString;
finally
JSONArray.Free;
end;
end;
procedure TTTW_Events.ProcessFileRequest(ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo; const Folder: string);
var
FileName: string;
FilePath: string;
FS: TFileStream;
begin
FileName := TPath.GetFileName(ARequestInfo.Document);
FilePath := myConst.AppDataPath + Folder + '\' + FileName;
if FileExists(FilePath) then
begin
try
FS := TFileStream.Create(FilePath, fmOpenRead + fmShareDenyWrite);
AResponseInfo.ContentStream := FS;
AResponseInfo.ContentType := GetMIMETypeFromFile(FilePath);;
AResponseInfo.ResponseNo := 200;
except
FS.Free;
AResponseInfo.ResponseNo := 500;
end;
end
else
begin
AResponseInfo.ResponseNo := 404;
end;
end;
procedure TTTW_Events.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '/' then
begin
AResponseInfo.CacheControl := 'no-cache, no-store, must-revalidate';
AResponseInfo.Pragma := 'no-cache';
AResponseInfo.Expires := 0;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := GenerateHTML;
end
else if ARequestInfo.Document = '/messages' then
begin
AResponseInfo.ContentType := 'application/json; charset=utf-8';
AResponseInfo.ContentText := GenerateJSON;
end
else if ARequestInfo.Document.StartsWith('/sounds/') then
begin
ProcessFileRequest(ARequestInfo, AResponseInfo, 'sounds');
end
else if ARequestInfo.Document.StartsWith('/fonts/') then
begin
ProcessFileRequest(ARequestInfo, AResponseInfo, 'fonts');
end
else if ARequestInfo.Document.StartsWith('/imgs/') then
begin
ProcessFileRequest(ARequestInfo, AResponseInfo, 'imgs');
end
else
AResponseInfo.ResponseNo := 404;
end;
procedure TTTW_Events.ActiveServer(aEn: boolean);
begin
IdHTTPServer1.Active := aEn;
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.