Skip to content

Commit 6e17162

Browse files
committed
Encryption methods are implemented in EncryptionMethods.cs, while HotkeyManagement.cs is enhanced for better hotkey management. Logging operations are centralized in Logging.cs, and OSC message handling is improved in OSCSender.cs.
Other modules, including `UpdateApp.cs`, `ComponentStatsModule.cs`, `IntelliChatModule.cs`, `MediaLinkModule.cs`, `NetworkStatisticsModule.cs`, `OpenAIModule.cs`, and `PulsoidModule.cs`, are updated to enhance functionality, manage data, and improve user interactions. The `SoundpadModule.cs`, `TTSModule.cs`, `WhisperModule.cs`, and `WindowActivityModule.cs` files are refactored for better performance and clarity.
1 parent 8d7389f commit 6e17162

29 files changed

+4744
-4121
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
3+
namespace MagicChatboxAPI.Events
4+
{
5+
/// <summary>
6+
/// Event arguments fired when a newly banned user is detected.
7+
/// </summary>
8+
public class BanDetectedEventArgs : EventArgs
9+
{
10+
/// <summary>
11+
/// The user ID found to be banned in the latest check.
12+
/// </summary>
13+
public string BannedUserId { get; }
14+
15+
/// <summary>
16+
/// Initializes a new instance of <see cref="BanDetectedEventArgs"/>.
17+
/// </summary>
18+
/// <param name="bannedUserId">ID of the newly banned user.</param>
19+
public BanDetectedEventArgs(string bannedUserId)
20+
{
21+
BannedUserId = bannedUserId ?? string.Empty;
22+
}
23+
}
24+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace MagicChatboxAPI.Enums
4+
{
5+
/// <summary>
6+
/// Detailed information from a check operation.
7+
/// </summary>
8+
public class VRChatUserCheckResult
9+
{
10+
/// <summary>
11+
/// Overall status of the check.
12+
/// </summary>
13+
public VRChatUserCheckStatus Status { get; set; }
14+
15+
/// <summary>
16+
/// Indicates if the check completed with any user
17+
/// being allowed or no new bans found.
18+
/// </summary>
19+
public bool AnyUserAllowed { get; set; }
20+
21+
/// <summary>
22+
/// Optional error message if something went wrong.
23+
/// </summary>
24+
public string ErrorMessage { get; set; }
25+
}
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace MagicChatboxAPI.Enums
4+
{
5+
/// <summary>
6+
/// Possible outcomes for user checks.
7+
/// </summary>
8+
public enum VRChatUserCheckStatus
9+
{
10+
Success = 0,
11+
NoFolderFound,
12+
NoUserIdsFound,
13+
ApiError,
14+
ApiTimeout,
15+
UnknownError
16+
}
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
using MagicChatboxAPI.Enums;
2+
using MagicChatboxAPI.Events;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net.Http.Json;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace MagicChatboxAPI.Services
11+
{
12+
13+
public interface IAllowedForUsingService
14+
{
15+
void StartUserMonitoring(TimeSpan interval);
16+
void StopUserMonitoring();
17+
18+
event EventHandler<BanDetectedEventArgs> BanDetected;
19+
}
20+
21+
public class AllowedForUsingService : IAllowedForUsingService
22+
{
23+
#region Constants and Fields
24+
25+
// External API endpoint for checking a user's ban status
26+
private const string ApiEndpoint = "https://api.magicchatbox.com/moderation/checkIfClientIsAllowed";
27+
28+
private readonly HttpClient _httpClient;
29+
30+
private Timer _timer;
31+
private bool _isMonitoring;
32+
private readonly object _monitorLock = new();
33+
34+
private List<string> _allUserIds;
35+
36+
private readonly Dictionary<string, bool> _userAllowedCache = new();
37+
38+
#endregion
39+
40+
#region Events
41+
42+
43+
public event EventHandler<BanDetectedEventArgs> BanDetected;
44+
45+
#endregion
46+
47+
#region Constructor
48+
49+
50+
public AllowedForUsingService()
51+
{
52+
_httpClient = new HttpClient();
53+
}
54+
55+
#endregion
56+
57+
#region Public Methods
58+
59+
public void StartUserMonitoring(TimeSpan interval)
60+
{
61+
lock (_monitorLock)
62+
{
63+
if (_isMonitoring)
64+
return;
65+
66+
_allUserIds = ScanAllVrChatUserIds();
67+
68+
foreach (var userId in _allUserIds)
69+
{
70+
_userAllowedCache[userId] = true;
71+
}
72+
73+
if (_allUserIds.Count == 0)
74+
{
75+
return;
76+
}
77+
78+
_timer = new Timer(async _ => await UserMonitorCallback(),
79+
null,
80+
TimeSpan.Zero,
81+
interval);
82+
_isMonitoring = true;
83+
}
84+
}
85+
86+
87+
public void StopUserMonitoring()
88+
{
89+
lock (_monitorLock)
90+
{
91+
if (!_isMonitoring)
92+
return;
93+
94+
_timer?.Dispose();
95+
_timer = null;
96+
_isMonitoring = false;
97+
}
98+
}
99+
100+
#endregion
101+
102+
#region Private Methods
103+
104+
/// <summary>
105+
/// Scans the VRChat OSC folder once, collecting all user IDs.
106+
/// </summary>
107+
/// <returns>List of all user IDs found (excluding "usr_" prefix).</returns>
108+
private List<string> ScanAllVrChatUserIds()
109+
{
110+
var userIds = new List<string>();
111+
112+
try
113+
{
114+
// Base path to VRChat's OSC user folders
115+
var basePath = Path.Combine(
116+
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
117+
"AppData", "LocalLow", "VRChat", "VRChat", "OSC");
118+
119+
// If folder doesn't exist, return empty
120+
if (!Directory.Exists(basePath))
121+
{
122+
Console.WriteLine($"[AllowedForUsingService] VRChat OSC folder not found: {basePath}");
123+
return userIds;
124+
}
125+
126+
// Get all directories matching "usr_*"
127+
var userDirectories = Directory.GetDirectories(basePath, "usr_*");
128+
if (userDirectories == null || userDirectories.Length == 0)
129+
{
130+
Console.WriteLine("[AllowedForUsingService] No user directories found.");
131+
return userIds;
132+
}
133+
134+
// Extract the user IDs
135+
foreach (var directory in userDirectories)
136+
{
137+
var directoryName = Path.GetFileName(directory);
138+
if (!string.IsNullOrEmpty(directoryName) && directoryName.StartsWith("usr_"))
139+
{
140+
var extractedUserId = directoryName.Substring("usr_".Length).Trim();
141+
if (!string.IsNullOrWhiteSpace(extractedUserId))
142+
{
143+
userIds.Add(extractedUserId);
144+
}
145+
}
146+
}
147+
}
148+
catch (Exception ex)
149+
{
150+
Console.WriteLine($"[AllowedForUsingService] Error scanning user IDs: {ex.Message}");
151+
}
152+
153+
return userIds.Distinct().ToList(); // Remove duplicates just in case
154+
}
155+
156+
/// <summary>
157+
/// Timer callback: checks the ban status of all known user IDs via API.
158+
/// Fires the BanDetected event immediately when a user transitions from allowed to banned.
159+
/// </summary>
160+
private async Task UserMonitorCallback()
161+
{
162+
if (_allUserIds == null || !_allUserIds.Any())
163+
return; // Skip if no users are loaded
164+
165+
try
166+
{
167+
// Iterate over known user IDs to check their ban status
168+
foreach (var userId in _allUserIds)
169+
{
170+
bool isCurrentlyAllowed = await CheckSingleUserAsync(userId);
171+
172+
lock (_userAllowedCache)
173+
{
174+
// If we have cached state for this user
175+
if (_userAllowedCache.TryGetValue(userId, out bool wasAllowed))
176+
{
177+
// If the user was previously allowed but now banned
178+
if (wasAllowed && !isCurrentlyAllowed)
179+
{
180+
// Update the cache for consistency
181+
_userAllowedCache[userId] = isCurrentlyAllowed;
182+
183+
// Fire the BanDetected event immediately with the banned user ID
184+
BanDetected?.Invoke(
185+
this,
186+
new BanDetectedEventArgs(userId)
187+
);
188+
189+
// Break out of the loop once a banned user is found
190+
// to trigger the event without checking further users
191+
return;
192+
}
193+
}
194+
else
195+
{
196+
// In case user is not present in the cache, add them
197+
_userAllowedCache[userId] = isCurrentlyAllowed;
198+
}
199+
200+
// Update the user's allowed status if no ban was detected
201+
_userAllowedCache[userId] = isCurrentlyAllowed;
202+
}
203+
}
204+
}
205+
catch (Exception ex)
206+
{
207+
// Log or handle exception as needed
208+
Console.WriteLine($"[AllowedForUsingService] Monitoring error: {ex.Message}");
209+
}
210+
}
211+
212+
213+
/// <summary>
214+
/// Calls the external API for a single user to determine if they are banned.
215+
/// Returns true if the user is allowed (not banned); false if banned.
216+
/// </summary>
217+
/// <param name="userId">Unique portion of the user ID (e.g., after 'usr_').</param>
218+
private async Task<bool> CheckSingleUserAsync(string userId)
219+
{
220+
var payload = new { userId };
221+
try
222+
{
223+
var response = await _httpClient.PostAsJsonAsync(ApiEndpoint, payload);
224+
225+
if (!response.IsSuccessStatusCode)
226+
{
227+
var errorContent = await response.Content.ReadAsStringAsync();
228+
Console.WriteLine($"[AllowedForUsingService] API returned {response.StatusCode}: {errorContent}");
229+
// Treat as banned (false) to be safe
230+
return true;
231+
}
232+
233+
var apiResponse = await response.Content.ReadFromJsonAsync<ApiResponse>();
234+
if (apiResponse == null)
235+
{
236+
Console.WriteLine("[AllowedForUsingService] API response was null.");
237+
// Treat as banned (false) to be safe
238+
return true;
239+
}
240+
241+
// If "isBanned" is true in the API response, the user is banned (not allowed).
242+
return !apiResponse.isBanned;
243+
}
244+
catch (Exception ex)
245+
{
246+
Console.WriteLine($"[AllowedForUsingService] CheckSingleUserAsync error for userId={userId}: {ex.Message}");
247+
// On exception, treat as banned for safety
248+
return true;
249+
}
250+
}
251+
252+
#endregion
253+
254+
#region Internal Model
255+
256+
/// <summary>
257+
/// Internal class that maps the JSON structure from the external API.
258+
/// Adjust properties to match the actual API response.
259+
/// </summary>
260+
private class ApiResponse
261+
{
262+
public string userId { get; set; }
263+
public bool isBanned { get; set; }
264+
}
265+
266+
#endregion
267+
}
268+
}

vrcosc-magicchatbox.sln

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.32112.339
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MagicChatbox", "vrcosc-magicchatbox\MagicChatbox.csproj", "{76FB3E35-94A5-445C-87F2-D75E9F701E5F}"
77
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicChatboxAPI", "MagicChatboxAPI\MagicChatboxAPI.csproj", "{C0B24731-C59E-4AC1-B4B7-988B254F645E}"
9+
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1012
Beta|Any CPU = Beta|Any CPU
@@ -18,6 +20,12 @@ Global
1820
{76FB3E35-94A5-445C-87F2-D75E9F701E5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
1921
{76FB3E35-94A5-445C-87F2-D75E9F701E5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
2022
{76FB3E35-94A5-445C-87F2-D75E9F701E5F}.Release|Any CPU.Build.0 = Release|Any CPU
23+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Beta|Any CPU.ActiveCfg = Debug|Any CPU
24+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Beta|Any CPU.Build.0 = Debug|Any CPU
25+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Debug|Any CPU.Build.0 = Debug|Any CPU
27+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Release|Any CPU.ActiveCfg = Release|Any CPU
28+
{C0B24731-C59E-4AC1-B4B7-988B254F645E}.Release|Any CPU.Build.0 = Release|Any CPU
2129
EndGlobalSection
2230
GlobalSection(SolutionProperties) = preSolution
2331
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)