Files
Ipseity-Demo/Assets/Editor/SceneNotifier.cs
T
2026-03-26 16:09:53 +01:00

203 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
[InitializeOnLoad]
public static class SceneNotifier
{
// Replace with your Discord webhook URL
private static readonly string WebhookUrl = "https://discord.com/api/webhooks/1486718530765328424/42mP_zYNTEwwBbc_lcuasPbpZppXoC4U14-Pde1YatdxatT5s9vml8lgQXTwAmm0PNe5";
private static readonly string UserName = Environment.UserName;
private static readonly string MachineName = Environment.MachineName;
private static readonly HttpClient Http = new HttpClient();
// Key = full scene path, Value = opened timestamp
private static readonly Dictionary<string, DateTime> OpenScenes = new Dictionary<string, DateTime>();
static SceneNotifier()
{
EditorSceneManager.sceneOpened += OnSceneOpened;
EditorSceneManager.sceneClosed += OnSceneClosed;
EditorApplication.quitting += OnEditorQuitting;
}
private static void OnSceneOpened(UnityEngine.SceneManagement.Scene scene, OpenSceneMode mode)
{
if (!IsTrackableScene(scene))
return;
string scenePath = scene.path;
DateTime openedAt = DateTime.Now;
OpenScenes[scenePath] = openedAt;
string message =
$"🚧 **Scene Locked**\n" +
$"👤 **{UserName}**\n" +
$"💻 **{MachineName}**\n" +
$"📁 `{FormatPath(scenePath)}`\n" +
$"🕒 Opened: **{FormatDateTime(openedAt)}**";
_ = SendDiscordMessage(message);
}
private static void OnSceneClosed(UnityEngine.SceneManagement.Scene scene)
{
if (!IsTrackableScene(scene))
return;
string scenePath = scene.path;
DateTime closedAt = DateTime.Now;
if (OpenScenes.TryGetValue(scenePath, out DateTime openedAt))
{
TimeSpan duration = closedAt - openedAt;
string message =
$"🔓 **Scene Released**\n" +
$"👤 **{UserName}**\n" +
$"💻 **{MachineName}**\n" +
$"📁 `{FormatPath(scenePath)}`\n" +
$"🕒 Opened: **{FormatDateTime(openedAt)}**\n" +
$"🕒 Closed: **{FormatDateTime(closedAt)}**\n" +
$"⏱ Stayed on scene: **{FormatDuration(duration)}**";
OpenScenes.Remove(scenePath);
_ = SendDiscordMessage(message);
}
else
{
// Safety fallback in case Unity reports a close for a scene we did not track
string message =
$"🔓 **Scene Released**\n" +
$"👤 **{UserName}**\n" +
$"💻 **{MachineName}**\n" +
$"📁 `{FormatPath(scenePath)}`\n" +
$"🕒 Closed: **{FormatDateTime(closedAt)}**\n" +
$"⏱ Stayed on scene: **unknown**";
_ = SendDiscordMessage(message);
}
}
private static void OnEditorQuitting()
{
DateTime quitAt = DateTime.Now;
if (OpenScenes.Count == 0)
{
string message =
$"❌ **Unity Closed**\n" +
$"👤 **{UserName}**\n" +
$"💻 **{MachineName}**\n" +
$"🕒 Closed Unity: **{FormatDateTime(quitAt)}**\n" +
$"📂 No tracked open scenes";
_ = SendDiscordMessage(message);
return;
}
// Copy first so we can safely clear afterward
var scenesToRelease = new List<KeyValuePair<string, DateTime>>(OpenScenes);
foreach (var kvp in scenesToRelease)
{
string scenePath = kvp.Key;
DateTime openedAt = kvp.Value;
TimeSpan duration = quitAt - openedAt;
string message =
$"❌ **Unity Closed — Scene Auto-Released**\n" +
$"👤 **{UserName}**\n" +
$"💻 **{MachineName}**\n" +
$"📁 `{FormatPath(scenePath)}`\n" +
$"🕒 Opened: **{FormatDateTime(openedAt)}**\n" +
$"🕒 Unity closed: **{FormatDateTime(quitAt)}**\n" +
$"⏱ Stayed on scene: **{FormatDuration(duration)}**";
_ = SendDiscordMessage(message);
}
OpenScenes.Clear();
}
private static bool IsTrackableScene(UnityEngine.SceneManagement.Scene scene)
{
// Prevent false notifications on startup / editor internals / untitled scenes
if (string.IsNullOrWhiteSpace(scene.path))
return false;
if (!scene.path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
return false;
return true;
}
private static string FormatPath(string path)
{
return path.Replace("Assets/", "");
}
private static string FormatDateTime(DateTime dt)
{
return dt.ToString("yyyy-MM-dd HH:mm:ss");
}
private static string FormatDuration(TimeSpan duration)
{
if (duration.TotalSeconds < 60)
return $"{(int)duration.TotalSeconds}s";
if (duration.TotalMinutes < 60)
return $"{(int)duration.TotalMinutes}m {duration.Seconds}s";
return $"{(int)duration.TotalHours}h {duration.Minutes}m {duration.Seconds}s";
}
private static async Task SendDiscordMessage(string content)
{
try
{
string escapedContent = EscapeJson(content);
string json = $"{{\"content\":\"{escapedContent}\"}}";
using var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
using var response = await Http.PostAsync(WebhookUrl, httpContent);
if (response.IsSuccessStatusCode)
{
Debug.Log($"[SceneNotifier] Sent to Discord:\n{content}");
}
else
{
string responseBody = await response.Content.ReadAsStringAsync();
Debug.LogWarning(
$"[SceneNotifier] Discord webhook failed. Status: {(int)response.StatusCode} {response.StatusCode}\n{responseBody}"
);
}
}
catch (Exception e)
{
Debug.LogWarning($"[SceneNotifier] Failed to notify Discord: {e.Message}");
}
}
private static string EscapeJson(string value)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
return value
.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\r", "\\r")
.Replace("\n", "\\n")
.Replace("\t", "\\t");
}
}