921 lines
29 KiB
C#
921 lines
29 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.Networking;
|
|
using WebSocketSharp;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace WefLab
|
|
{
|
|
/// <summary>
|
|
/// Donation data structure
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationData
|
|
{
|
|
public string platform;
|
|
public string donationType;
|
|
public int amount;
|
|
public string message;
|
|
public string donorName;
|
|
public long timestamp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unity event with donation data parameter
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationEvent : UnityEvent<DonationData> { }
|
|
|
|
/// <summary>
|
|
/// Donation event trigger based on amount range
|
|
/// </summary>
|
|
[System.Serializable]
|
|
public class DonationEventTrigger
|
|
{
|
|
[Tooltip("Name for this donation trigger (e.g., 'Small Donation', 'Large Donation')")]
|
|
public string triggerName = "New Trigger";
|
|
|
|
[Header("Amount Range")]
|
|
[Tooltip("Minimum donation amount (inclusive)")]
|
|
public int minAmount = 0;
|
|
|
|
[Tooltip("Maximum donation amount (inclusive, -1 for unlimited)")]
|
|
public int maxAmount = -1;
|
|
|
|
[Header("Repeat Settings")]
|
|
[Tooltip("Use amount-based repeat count (divide amount by amountPerRepeat)")]
|
|
public bool useAmountBasedRepeat = false;
|
|
|
|
[Tooltip("Amount of KRW per repeat (e.g., 1000 = 1 repeat per 1000 KRW)")]
|
|
[Min(1)]
|
|
public int amountPerRepeat = 1000;
|
|
|
|
[Tooltip("Minimum repeat count")]
|
|
[Min(1)]
|
|
public int minRepeatCount = 1;
|
|
|
|
[Tooltip("Maximum repeat count (0 = unlimited)")]
|
|
[Min(0)]
|
|
public int maxRepeatCount = 20;
|
|
|
|
[Tooltip("Fixed repeat count (used when useAmountBasedRepeat is false)")]
|
|
[Min(1)]
|
|
public int fixedRepeatCount = 1;
|
|
|
|
[Tooltip("Delay between each repeat in seconds (0 for immediate)")]
|
|
[Min(0)]
|
|
public float repeatDelay = 0.15f;
|
|
|
|
[Header("Event")]
|
|
[Tooltip("Unity event to trigger when donation amount is in range")]
|
|
public DonationEvent onDonation;
|
|
|
|
/// <summary>
|
|
/// Check if donation amount is within range
|
|
/// </summary>
|
|
public bool IsInRange(int amount)
|
|
{
|
|
if (amount < minAmount)
|
|
return false;
|
|
|
|
if (maxAmount >= 0 && amount > maxAmount)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate repeat count based on donation amount
|
|
/// </summary>
|
|
public int GetRepeatCount(int amount)
|
|
{
|
|
if (!useAmountBasedRepeat)
|
|
{
|
|
return fixedRepeatCount;
|
|
}
|
|
|
|
// Calculate repeat count based on amount
|
|
int count = amount / amountPerRepeat;
|
|
|
|
// Apply min/max limits
|
|
count = Mathf.Max(count, minRepeatCount);
|
|
if (maxRepeatCount > 0)
|
|
{
|
|
count = Mathf.Min(count, maxRepeatCount);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// WefLab WebSocket client for receiving donation events
|
|
/// Implements Engine.IO v4 and Socket.IO protocol
|
|
/// </summary>
|
|
public class WefLabWebSocketClient : MonoBehaviour
|
|
{
|
|
[Header("WefLab Settings")]
|
|
[Tooltip("WefLab page URL (e.g., https://weflab.com/page/guPK2ODAmmRvYG4)")]
|
|
public string pageUrl = "https://weflab.com/page/guPK2ODAmmRvYG4";
|
|
|
|
[Header("Donation Event Triggers")]
|
|
[Tooltip("Donation event triggers based on amount ranges")]
|
|
public DonationEventTrigger[] donationTriggers = Array.Empty<DonationEventTrigger>();
|
|
|
|
[Header("Queue Settings")]
|
|
[Tooltip("Enable sequential processing of donations")]
|
|
public bool enableQueue = true;
|
|
|
|
[Tooltip("Delay between each donation alert (seconds)")]
|
|
[Min(0.1f)]
|
|
public float alertDelay = 3f;
|
|
|
|
[Tooltip("Maximum queue size (0 = unlimited)")]
|
|
[Min(0)]
|
|
public int maxQueueSize = 50;
|
|
|
|
// Queue state (visible in Inspector for debugging)
|
|
[Header("Queue Status (Read Only)")]
|
|
[SerializeField] private int queueCount = 0;
|
|
[SerializeField] private bool isProcessingQueue = false;
|
|
|
|
// Donation queue
|
|
private Queue<DonationData> donationQueue = new Queue<DonationData>();
|
|
private Coroutine queueProcessorCoroutine = null;
|
|
|
|
// Hidden connection info
|
|
[HideInInspector] public bool isConnected = false;
|
|
[HideInInspector] public string currentSid = "";
|
|
[HideInInspector] public string extractedUserIdx = "";
|
|
|
|
// WebSocket connection
|
|
private WebSocket ws;
|
|
private string userIdx = ""; // Will be extracted from page
|
|
private string socketSid = "";
|
|
private bool isExtracting = false;
|
|
|
|
// Ping/Pong settings
|
|
private float pingInterval = 30f;
|
|
private float lastPingTime = 0f;
|
|
|
|
// Thread-safe action queue for main thread
|
|
private Queue<Action> mainThreadActions = new Queue<Action>();
|
|
private object actionLock = new object();
|
|
|
|
void Start()
|
|
{
|
|
// Extract user idx from page URL and then connect
|
|
StartCoroutine(ExtractUserIdxAndConnect());
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Execute queued actions on main thread
|
|
lock (actionLock)
|
|
{
|
|
while (mainThreadActions.Count > 0)
|
|
{
|
|
mainThreadActions.Dequeue()?.Invoke();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract user index from page URL and connect
|
|
/// </summary>
|
|
private IEnumerator ExtractUserIdxAndConnect()
|
|
{
|
|
if (string.IsNullOrEmpty(pageUrl))
|
|
{
|
|
Debug.LogError("[WefLab] Page URL is empty! Please set the page URL in Inspector.");
|
|
yield break;
|
|
}
|
|
|
|
isExtracting = true;
|
|
Debug.Log($"[WefLab] Fetching page URL: {pageUrl}");
|
|
|
|
// Fetch the page HTML
|
|
using (UnityWebRequest request = UnityWebRequest.Get(pageUrl))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.LogError($"[WefLab] Failed to fetch page: {request.error}");
|
|
isExtracting = false;
|
|
yield break;
|
|
}
|
|
|
|
string htmlContent = request.downloadHandler.text;
|
|
|
|
// Extract userIdx from JavaScript in the page
|
|
// Look for pattern: loginData = {...}
|
|
Match loginDataMatch = Regex.Match(htmlContent, @"loginData\s*=\s*(\{[^;]+\});", RegexOptions.Singleline);
|
|
|
|
if (loginDataMatch.Success)
|
|
{
|
|
try
|
|
{
|
|
string loginDataJson = loginDataMatch.Groups[1].Value;
|
|
var loginData = JsonConvert.DeserializeObject<JObject>(loginDataJson);
|
|
|
|
// Extract idx from loginData
|
|
userIdx = loginData["idx"]?.ToString() ?? "";
|
|
|
|
if (!string.IsNullOrEmpty(userIdx))
|
|
{
|
|
extractedUserIdx = userIdx;
|
|
Debug.Log($"[WefLab] Successfully extracted userIdx: {userIdx}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] userIdx not found in loginData");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing loginData: {ex.Message}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] Could not find loginData in page HTML");
|
|
}
|
|
}
|
|
|
|
isExtracting = false;
|
|
|
|
// Connect if we successfully extracted the userIdx
|
|
if (!string.IsNullOrEmpty(userIdx))
|
|
{
|
|
Connect();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[WefLab] Cannot connect - userIdx extraction failed");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to WefLab WebSocket server
|
|
/// </summary>
|
|
public void Connect()
|
|
{
|
|
if (ws != null && ws.IsAlive)
|
|
{
|
|
Debug.LogWarning("[WefLab] Already connected");
|
|
return;
|
|
}
|
|
|
|
// Build WebSocket URL
|
|
string wsUrl = $"wss://ssmain.weflab.com/socket.io/?idx={userIdx}&type=page&page=alert&EIO=4&transport=websocket";
|
|
|
|
Debug.Log($"[WefLab] Connecting to: {wsUrl}");
|
|
|
|
ws = new WebSocket(wsUrl);
|
|
|
|
// Event handlers
|
|
ws.OnOpen += OnWebSocketOpen;
|
|
ws.OnMessage += OnWebSocketMessage;
|
|
ws.OnError += OnWebSocketError;
|
|
ws.OnClose += OnWebSocketClose;
|
|
|
|
// Connect
|
|
ws.ConnectAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from WebSocket
|
|
/// </summary>
|
|
public void Disconnect()
|
|
{
|
|
if (ws != null)
|
|
{
|
|
ws.Close();
|
|
ws = null;
|
|
}
|
|
|
|
isConnected = false;
|
|
socketSid = "";
|
|
currentSid = "";
|
|
}
|
|
|
|
#region WebSocket Event Handlers
|
|
|
|
private void OnWebSocketOpen(object sender, EventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.Log("[WefLab] WebSocket connection opened");
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketMessage(object sender, MessageEventArgs e)
|
|
{
|
|
string data = e.Data;
|
|
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
ProcessMessage(data);
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketError(object sender, ErrorEventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.LogError($"[WefLab] WebSocket error: {e.Message}");
|
|
if (e.Exception != null)
|
|
{
|
|
Debug.LogError($"[WefLab] Exception: {e.Exception}");
|
|
}
|
|
});
|
|
}
|
|
|
|
private void OnWebSocketClose(object sender, CloseEventArgs e)
|
|
{
|
|
EnqueueMainThreadAction(() =>
|
|
{
|
|
Debug.Log($"[WefLab] WebSocket closed. Code: {e.Code}, Reason: {e.Reason}");
|
|
isConnected = false;
|
|
socketSid = "";
|
|
currentSid = "";
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Message Processing
|
|
|
|
/// <summary>
|
|
/// Process incoming WebSocket message
|
|
/// </summary>
|
|
private void ProcessMessage(string data)
|
|
{
|
|
if (string.IsNullOrEmpty(data))
|
|
return;
|
|
|
|
// Get message type (first character or first two characters)
|
|
string messageType = data.Length >= 2 && char.IsDigit(data[1])
|
|
? data.Substring(0, 2)
|
|
: data.Substring(0, 1);
|
|
|
|
string payload = data.Substring(messageType.Length);
|
|
|
|
switch (messageType)
|
|
{
|
|
case "0": // Engine.IO OPEN
|
|
HandleEngineOpen(payload);
|
|
break;
|
|
|
|
case "2": // Engine.IO PING
|
|
HandlePing();
|
|
break;
|
|
|
|
case "3": // Engine.IO PONG
|
|
Debug.Log("[WefLab] Received PONG");
|
|
break;
|
|
|
|
case "40": // Socket.IO CONNECT
|
|
HandleSocketConnect(payload);
|
|
break;
|
|
|
|
case "42": // Socket.IO EVENT
|
|
HandleSocketEvent(payload);
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"[WefLab] Unknown message type: {messageType} | Data: {data}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Engine.IO OPEN message (handshake)
|
|
/// </summary>
|
|
private void HandleEngineOpen(string payload)
|
|
{
|
|
try
|
|
{
|
|
var openData = JsonConvert.DeserializeObject<JObject>(payload);
|
|
currentSid = openData["sid"]?.ToString() ?? "";
|
|
|
|
if (openData["pingInterval"] != null)
|
|
{
|
|
pingInterval = openData["pingInterval"].Value<float>() / 1000f; // Convert to seconds
|
|
}
|
|
|
|
Debug.Log($"[WefLab] Engine.IO OPEN - SID: {currentSid}, PingInterval: {pingInterval}s");
|
|
Debug.Log($"[WefLab] Full handshake data: {payload}");
|
|
|
|
// Send Socket.IO CONNECT
|
|
SendSocketConnect();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing OPEN message: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Engine.IO PING
|
|
/// </summary>
|
|
private void HandlePing()
|
|
{
|
|
Debug.Log("[WefLab] Received PING, sending PONG");
|
|
SendMessage("3"); // Send PONG
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Socket.IO CONNECT response
|
|
/// </summary>
|
|
private void HandleSocketConnect(string payload)
|
|
{
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(payload))
|
|
{
|
|
var connectData = JsonConvert.DeserializeObject<JObject>(payload);
|
|
socketSid = connectData["sid"]?.ToString() ?? "";
|
|
Debug.Log($"[WefLab] Socket.IO CONNECT - SID: {socketSid}");
|
|
Debug.Log($"[WefLab] Full connect data: {payload}");
|
|
}
|
|
|
|
isConnected = true;
|
|
|
|
// Send join message
|
|
SendJoinMessage();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing Socket.IO CONNECT: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle Socket.IO EVENT message (donation data)
|
|
/// </summary>
|
|
private void HandleSocketEvent(string payload)
|
|
{
|
|
try
|
|
{
|
|
var eventArray = JsonConvert.DeserializeObject<JArray>(payload);
|
|
|
|
if (eventArray == null || eventArray.Count < 2)
|
|
{
|
|
Debug.LogWarning($"[WefLab] Invalid event format: {payload}");
|
|
return;
|
|
}
|
|
|
|
string eventName = eventArray[0].ToString();
|
|
var eventData = eventArray[1] as JObject;
|
|
|
|
Debug.Log($"[WefLab] ========== EVENT RECEIVED ==========");
|
|
Debug.Log($"[WefLab] Event Name: {eventName}");
|
|
Debug.Log($"[WefLab] Event Data: {eventData?.ToString(Formatting.Indented)}");
|
|
Debug.Log($"[WefLab] ===================================");
|
|
|
|
if (eventName == "msg")
|
|
{
|
|
HandleMessageEvent(eventData);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"[WefLab] Error parsing Socket.IO EVENT: {ex.Message}");
|
|
Debug.LogError($"[WefLab] Payload: {payload}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle "msg" event (donation notifications)
|
|
/// </summary>
|
|
private void HandleMessageEvent(JObject msgData)
|
|
{
|
|
if (msgData == null)
|
|
return;
|
|
|
|
string msgType = msgData["type"]?.ToString() ?? "";
|
|
|
|
switch (msgType)
|
|
{
|
|
case "test_donation":
|
|
HandleTestDonation(msgData);
|
|
break;
|
|
|
|
case "donation":
|
|
case "SENDBALLOON":
|
|
case "cheese":
|
|
case "superchat":
|
|
case "bits":
|
|
HandleDonation(msgData);
|
|
break;
|
|
|
|
default:
|
|
Debug.Log($"[WefLab] Unhandled message type: {msgType}");
|
|
Debug.Log($"[WefLab] Data: {msgData.ToString(Formatting.Indented)}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Normalize donation amount to unified currency (Chzzk standard: 1 = 1 KRW)
|
|
/// SOOP (Afreeca): 1 balloon = 100 KRW → multiply by 100
|
|
/// Chzzk: 1 cheese = 1 KRW → no conversion
|
|
/// YouTube: Keep as-is (already in KRW equivalent)
|
|
/// Twitch: Keep as-is
|
|
/// </summary>
|
|
private static int NormalizeDonationAmount(string platform, int rawAmount)
|
|
{
|
|
platform = platform.ToLower();
|
|
|
|
if (platform == "afreeca" || platform == "soop")
|
|
{
|
|
// SOOP: 1 balloon = 100 KRW
|
|
return rawAmount * 100;
|
|
}
|
|
|
|
// Chzzk, YouTube, Twitch: no conversion needed
|
|
return rawAmount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle test donation event
|
|
/// </summary>
|
|
private void HandleTestDonation(JObject msgData)
|
|
{
|
|
if (msgData["data"] is not JObject donationDataJson)
|
|
return;
|
|
|
|
string platform = donationDataJson["platform"]?.ToString() ?? "unknown";
|
|
string type = donationDataJson["type"]?.ToString() ?? "unknown";
|
|
int rawAmount = 0;
|
|
|
|
// Parse amount
|
|
if (int.TryParse(donationDataJson["value"]?.ToString(), out int parsedAmount))
|
|
{
|
|
rawAmount = parsedAmount;
|
|
}
|
|
|
|
// Normalize amount based on platform
|
|
int amount = NormalizeDonationAmount(platform, rawAmount);
|
|
|
|
string message = donationDataJson["msg"]?.ToString() ?? "";
|
|
long timestamp = donationDataJson["time"]?.Value<long>() ?? 0;
|
|
|
|
Debug.Log($"[WefLab] *** TEST DONATION ***");
|
|
Debug.Log($"[WefLab] Platform: {platform}");
|
|
Debug.Log($"[WefLab] Type: {type}");
|
|
Debug.Log($"[WefLab] Raw Amount: {rawAmount} → Normalized: {amount} KRW");
|
|
Debug.Log($"[WefLab] Message: {message}");
|
|
Debug.Log($"[WefLab] Timestamp: {timestamp}");
|
|
|
|
// Create donation data and trigger events
|
|
DonationData donation = new()
|
|
{
|
|
platform = platform,
|
|
donationType = type,
|
|
amount = amount,
|
|
message = message,
|
|
donorName = "Test Donor",
|
|
timestamp = timestamp
|
|
};
|
|
|
|
EnqueueDonation(donation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle real donation event
|
|
/// </summary>
|
|
private void HandleDonation(JObject msgData)
|
|
{
|
|
Debug.Log($"[WefLab] *** REAL DONATION ***");
|
|
Debug.Log($"[WefLab] Full donation data: {msgData.ToString(Formatting.Indented)}");
|
|
|
|
if (msgData["data"] is not JObject donationDataJson)
|
|
return;
|
|
|
|
string platform = msgData["platform"]?.ToString() ?? "unknown";
|
|
string type = msgData["type"]?.ToString() ?? "unknown";
|
|
int rawAmount = 0;
|
|
|
|
// Parse amount from different possible fields
|
|
if (donationDataJson["value"] != null && int.TryParse(donationDataJson["value"].ToString(), out int parsedValue))
|
|
{
|
|
rawAmount = parsedValue;
|
|
}
|
|
else if (donationDataJson["amount"] != null && int.TryParse(donationDataJson["amount"].ToString(), out int parsedAmount))
|
|
{
|
|
rawAmount = parsedAmount;
|
|
}
|
|
|
|
// Normalize amount based on platform
|
|
int amount = NormalizeDonationAmount(platform, rawAmount);
|
|
|
|
string message = donationDataJson["msg"]?.ToString() ?? donationDataJson["message"]?.ToString() ?? "";
|
|
string donorName = donationDataJson["nickname"]?.ToString() ?? donationDataJson["name"]?.ToString() ?? "Anonymous";
|
|
long timestamp = donationDataJson["time"]?.Value<long>() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
|
|
// Create donation data
|
|
DonationData donation = new()
|
|
{
|
|
platform = platform,
|
|
donationType = type,
|
|
amount = amount,
|
|
message = message,
|
|
donorName = donorName,
|
|
timestamp = timestamp
|
|
};
|
|
|
|
Debug.Log($"[WefLab] Donation: {donorName} - {rawAmount} → {amount} KRW ({platform})");
|
|
|
|
// Enqueue donation for sequential processing
|
|
EnqueueDonation(donation);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueue donation for sequential processing
|
|
/// </summary>
|
|
private void EnqueueDonation(DonationData donation)
|
|
{
|
|
if (enableQueue)
|
|
{
|
|
// Check queue size limit
|
|
if (maxQueueSize > 0 && donationQueue.Count >= maxQueueSize)
|
|
{
|
|
Debug.LogWarning($"[WefLab] Queue full ({maxQueueSize}), dropping oldest donation");
|
|
donationQueue.Dequeue();
|
|
}
|
|
|
|
donationQueue.Enqueue(donation);
|
|
queueCount = donationQueue.Count;
|
|
Debug.Log($"[WefLab] Donation queued. Queue size: {queueCount}");
|
|
|
|
// Start queue processor if not running
|
|
if (!isProcessingQueue)
|
|
{
|
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Direct trigger without queue (original behavior)
|
|
TriggerDonationEvents(donation);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process donation queue sequentially with fixed delay
|
|
/// </summary>
|
|
private IEnumerator ProcessDonationQueue()
|
|
{
|
|
isProcessingQueue = true;
|
|
|
|
while (donationQueue.Count > 0)
|
|
{
|
|
var donation = donationQueue.Dequeue();
|
|
queueCount = donationQueue.Count;
|
|
|
|
// Trigger events
|
|
TriggerDonationEvents(donation);
|
|
|
|
Debug.Log($"[WefLab] Alert triggered. Waiting {alertDelay:F1}s. Remaining in queue: {donationQueue.Count}");
|
|
|
|
// Wait for fixed delay
|
|
yield return new WaitForSeconds(alertDelay);
|
|
}
|
|
|
|
isProcessingQueue = false;
|
|
queueProcessorCoroutine = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Trigger donation events based on amount range
|
|
/// </summary>
|
|
private void TriggerDonationEvents(DonationData donation)
|
|
{
|
|
if (donationTriggers == null || donationTriggers.Length == 0)
|
|
{
|
|
Debug.LogWarning("[WefLab] No donation triggers configured");
|
|
return;
|
|
}
|
|
|
|
bool triggeredAny = false;
|
|
|
|
// Check each trigger
|
|
foreach (var trigger in donationTriggers)
|
|
{
|
|
if (trigger == null)
|
|
continue;
|
|
|
|
// Check if donation amount is in range
|
|
if (trigger.IsInRange(donation.amount))
|
|
{
|
|
int repeatCount = trigger.GetRepeatCount(donation.amount);
|
|
Debug.Log($"[WefLab] Triggering: {trigger.triggerName} (Amount: {donation.amount}, Range: {trigger.minAmount}-{(trigger.maxAmount >= 0 ? trigger.maxAmount.ToString() : "unlimited")}, Repeat: {repeatCount}x)");
|
|
|
|
// Start coroutine to handle repeated invocation
|
|
StartCoroutine(InvokeRepeatedEvent(trigger, donation, repeatCount));
|
|
triggeredAny = true;
|
|
}
|
|
}
|
|
|
|
if (!triggeredAny)
|
|
{
|
|
Debug.LogWarning($"[WefLab] No triggers matched for donation amount: {donation.amount}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to invoke event multiple times with delay
|
|
/// </summary>
|
|
private IEnumerator InvokeRepeatedEvent(DonationEventTrigger trigger, DonationData donation, int repeatCount)
|
|
{
|
|
for (int i = 0; i < repeatCount; i++)
|
|
{
|
|
// Invoke the event
|
|
trigger.onDonation?.Invoke(donation);
|
|
|
|
// Wait for delay before next repetition (skip on last iteration)
|
|
if (i < repeatCount - 1 && trigger.repeatDelay > 0)
|
|
{
|
|
yield return new WaitForSeconds(trigger.repeatDelay);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Send Messages
|
|
|
|
/// <summary>
|
|
/// Send Socket.IO CONNECT message (40)
|
|
/// </summary>
|
|
private void SendSocketConnect()
|
|
{
|
|
Debug.Log("[WefLab] Sending Socket.IO CONNECT (40)");
|
|
SendMessage("40");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send join message to subscribe to donation events
|
|
/// </summary>
|
|
private void SendJoinMessage()
|
|
{
|
|
var joinData = new
|
|
{
|
|
type = "join",
|
|
page = "page",
|
|
idx = userIdx,
|
|
pageid = "alert",
|
|
preset = "0"
|
|
};
|
|
|
|
var message = new object[] { "msg", joinData };
|
|
string json = JsonConvert.SerializeObject(message);
|
|
string fullMessage = "42" + json;
|
|
|
|
Debug.Log($"[WefLab] Sending JOIN message: {fullMessage}");
|
|
SendMessage(fullMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send raw message through WebSocket
|
|
/// </summary>
|
|
private void SendMessage(string message)
|
|
{
|
|
if (ws != null && ws.IsAlive)
|
|
{
|
|
ws.Send(message);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[WefLab] Cannot send message - WebSocket not connected");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Thread Safety
|
|
|
|
/// <summary>
|
|
/// Enqueue action to be executed on main thread
|
|
/// </summary>
|
|
private void EnqueueMainThreadAction(Action action)
|
|
{
|
|
lock (actionLock)
|
|
{
|
|
mainThreadActions.Enqueue(action);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Reconnect to WebSocket
|
|
/// </summary>
|
|
public void Reconnect()
|
|
{
|
|
Disconnect();
|
|
Connect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if connected
|
|
/// </summary>
|
|
public bool IsConnected()
|
|
{
|
|
return isConnected && ws != null && ws.IsAlive;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current queue count
|
|
/// </summary>
|
|
public int GetQueueCount()
|
|
{
|
|
return donationQueue.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if queue is being processed
|
|
/// </summary>
|
|
public bool IsQueueProcessing()
|
|
{
|
|
return isProcessingQueue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all pending donations in queue
|
|
/// </summary>
|
|
public void ClearQueue()
|
|
{
|
|
donationQueue.Clear();
|
|
queueCount = 0;
|
|
Debug.Log("[WefLab] Queue cleared");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skip current alert and move to next in queue
|
|
/// </summary>
|
|
public void SkipCurrentAlert()
|
|
{
|
|
if (queueProcessorCoroutine != null)
|
|
{
|
|
StopCoroutine(queueProcessorCoroutine);
|
|
queueProcessorCoroutine = null;
|
|
}
|
|
|
|
if (donationQueue.Count > 0)
|
|
{
|
|
Debug.Log("[WefLab] Skipping to next alert");
|
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
|
}
|
|
else
|
|
{
|
|
isProcessingQueue = false;
|
|
Debug.Log("[WefLab] No more alerts in queue");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pause queue processing
|
|
/// </summary>
|
|
public void PauseQueue()
|
|
{
|
|
if (queueProcessorCoroutine != null)
|
|
{
|
|
StopCoroutine(queueProcessorCoroutine);
|
|
queueProcessorCoroutine = null;
|
|
isProcessingQueue = false;
|
|
Debug.Log("[WefLab] Queue paused");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume queue processing
|
|
/// </summary>
|
|
public void ResumeQueue()
|
|
{
|
|
if (!isProcessingQueue && donationQueue.Count > 0)
|
|
{
|
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
|
Debug.Log("[WefLab] Queue resumed");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|