Skip to content
This repository was archived by the owner on Mar 8, 2026. It is now read-only.

Commit 586ddc6

Browse files
committed
colored nametags
(but at what cost)
1 parent 23424f3 commit 586ddc6

File tree

2 files changed

+68
-11
lines changed

2 files changed

+68
-11
lines changed

SCHIZO/SwarmControl/Redeems/PayAttentionToMeStreamer/NameTagErmfish.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Threading.Tasks;
56
using SCHIZO.Commands.Base;
67
using SCHIZO.Commands.Context;
78
using SCHIZO.Commands.Input;
89
using SCHIZO.Commands.Output;
910
using SCHIZO.Helpers;
11+
using SCHIZO.Twitch;
12+
using SwarmControl.Models.Game.Messages;
1013
using UnityEngine;
1114
using UWE;
1215

@@ -51,13 +54,13 @@ protected override object ExecuteCore(CommandExecutionContext ctx)
5154
});
5255

5356
RemoteInput? input = ctx.Input as RemoteInput;
54-
string? user = input?.Model.GetDisplayName();
55-
if (string.IsNullOrEmpty(user))
57+
TwitchUser? user = input?.Model.User;
58+
if (user is not { DisplayName: { } dispName })
5659
return CommonResults.Error("Could not get username");
5760

5861
if (ermfish)
5962
{
60-
SetNameTag(ermfish, user);
63+
CoroutineHost.StartCoroutine(SetNameTag(ermfish, user));
6164
return CommonResults.OK();
6265
}
6366
else
@@ -67,7 +70,7 @@ protected override object ExecuteCore(CommandExecutionContext ctx)
6770
}
6871
}
6972

70-
private IEnumerator SpawnCoro(string? user)
73+
private IEnumerator SpawnCoro(TwitchUser? user)
7174
{
7275
if (!_ermfishPrefab)
7376
{
@@ -82,15 +85,21 @@ private IEnumerator SpawnCoro(string? user)
8285

8386
GameObject ermfish = Utils.CreatePrefab(_ermfishPrefab);
8487
yield return null;
85-
SetNameTag(ermfish, user);
88+
yield return SetNameTag(ermfish, user);
8689
}
8790

88-
private void SetNameTag(GameObject ermfish, string? user)
91+
private IEnumerator SetNameTag(GameObject ermfish, TwitchUser? user)
8992
{
93+
if (user is not { DisplayName: { } dispName }) yield break;
94+
9095
NameTag tag = ermfish.GetComponentInChildren<NameTag>(true);
91-
if (!tag || tag.isActiveAndEnabled) return;
96+
if (!tag || tag.isActiveAndEnabled) yield break;
9297

93-
tag.textMesh.text = user;
98+
tag.textMesh.text = dispName;
9499
tag.gameObject.SetActive(true);
100+
101+
Task<Color> task = TwitchIntegration.GetUserChatColor(user.Id);
102+
yield return task.PoorMansAwait();
103+
tag.textMesh.color = task.Result;
95104
}
96105
}

SCHIZO/Twitch/TwitchIntegration.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
46
using System.Threading.Tasks;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Serialization;
59
using SCHIZO.Commands.Attributes;
610
using SCHIZO.Helpers;
711
using SCHIZO.SwarmControl;
@@ -56,6 +60,7 @@ private void Awake()
5660
ConnectionCredentials credentials = new(username, token);
5761

5862
_client.Initialize(credentials, targetChannel);
63+
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {token}");
5964

6065
_client.Connect();
6166
}
@@ -111,8 +116,8 @@ public static string SetTwitchLogin(string username, string token)
111116
return "Twitch login updated. Please restart Subnautica.";
112117
}
113118

114-
private static ConcurrentDictionary<string, (Action<string> Callback, float ModerationDelay)> _nextMessageCallbacks = [];
115-
private static ConcurrentDictionary<string, Action> _timeoutCallbacks = [];
119+
private static readonly ConcurrentDictionary<string, (Action<string> Callback, float ModerationDelay)> _nextMessageCallbacks = [];
120+
private static readonly ConcurrentDictionary<string, Action> _timeoutCallbacks = [];
116121
public static void AddNextMessageCallback(TwitchUser user, Action<string> callback, float moderationDelay = 5f)
117122
{
118123
_nextMessageCallbacks.AddOrUpdate(user.Id, (callback, moderationDelay), (_, existing) =>
@@ -136,7 +141,7 @@ private static void InvokeCallbacksOnMessage(TwitchUser user, string message)
136141
}
137142
bool timedOut = false;
138143
Action cb = () => timedOut = true;
139-
_timeoutCallbacks.AddOrUpdate(user.Id, cb, (_, existing) => existing + cb);
144+
_timeoutCallbacks.AddOrUpdate(user.Id, cb, (_, curr) => curr + cb);
140145
Task.Delay(TimeSpan.FromSeconds(existing.delay)).ContinueWith(__ =>
141146
{
142147
_timeoutCallbacks.TryRemove(user.Id, out _);
@@ -145,4 +150,47 @@ private static void InvokeCallbacksOnMessage(TwitchUser user, string message)
145150
SwarmControlManager.Instance.QueueOnMainThread(() => existing.callback(message));
146151
});
147152
}
153+
154+
private static readonly Dictionary<string, Color> _colors = [];
155+
private static bool _dead; // in case clientid is wrong and twitch api call fails
156+
157+
public static async Task<Color> GetUserChatColor(string userId)
158+
{
159+
if (_colors.TryGetValue(userId, out Color cached)) return cached;
160+
if (_dead) return Color.white;
161+
162+
try
163+
{
164+
// manual http call due to https://github.com/TwitchLib/TwitchLib/issues/1126
165+
string respJson = await _httpClient.GetStringAsync($"helix/chat/color?user_id={userId}");
166+
if (string.IsNullOrEmpty(respJson)) return Color.white;
167+
168+
UserColorResponseModel resp = JsonConvert.DeserializeObject<UserColorResponseModel>(respJson);
169+
string c = resp.Data[0].Color;
170+
return _colors[userId] = ColorUtility.TryParseHtmlString(c, out Color color)
171+
? color
172+
: Color.white;
173+
}
174+
catch (Exception e)
175+
{
176+
LOGGER.LogError($"Failed to get user color for {userId}: {e}");
177+
_dead = true;
178+
return Color.white;
179+
}
180+
}
181+
182+
private static readonly HttpClient _httpClient = new()
183+
{
184+
BaseAddress = new("https://api.twitch.tv/"),
185+
DefaultRequestHeaders = { { "Client-Id", "gp762nuuoqcoxypju8c569th9wz7q5" } }, // https://twitchtokengenerator.com
186+
};
187+
188+
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
189+
internal class UserColorResponseModel
190+
{
191+
public UserColorResponseItemModel[] Data { get; set; }
192+
193+
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
194+
public record UserColorResponseItemModel(string UserId, string UserName, string UserLogin, string Color);
195+
}
148196
}

0 commit comments

Comments
 (0)