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

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.