Skip to content

Commit 4c106c6

Browse files
authored
Feature sentiment gauge (#429)
* Completed Sentiment Gauge and ToDo widget
1 parent f8984b6 commit 4c106c6

File tree

9 files changed

+443
-32
lines changed

9 files changed

+443
-32
lines changed

Fritz.Chatbot/Commands/ProjectCommand.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
using System;
22
using System.Threading.Tasks;
33
using Fritz.StreamLib.Core;
4+
using Fritz.StreamTools.Hubs;
5+
using Microsoft.AspNetCore.SignalR;
46
using Microsoft.Extensions.Configuration;
57

68
namespace Fritz.Chatbot.Commands
79
{
810
public class ProjectCommand : IBasicCommand2
911
{
1012
private readonly IConfiguration Configuration;
13+
private readonly IHubContext<ObsHub> _HubContext;
1114

12-
public ProjectCommand(IConfiguration configuration)
15+
public ProjectCommand(IConfiguration configuration, IHubContext<ObsHub> hubContext)
1316
{
1417
this.Configuration = configuration;
18+
_HubContext = hubContext;
1519
}
1620

1721
public string Trigger => "project";
@@ -20,13 +24,14 @@ public ProjectCommand(IConfiguration configuration)
2024

2125
public TimeSpan? Cooldown => TimeSpan.FromSeconds(30);
2226

23-
private static string CurrentProject = null;
27+
public static string CurrentProject { get; private set; }
2428

2529
public async Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory<char> rhs)
2630
{
2731
if ((isModerator || isBroadcaster) && !rhs.IsEmpty)
2832
{
2933
CurrentProject = rhs.ToString();
34+
await _HubContext.Clients.All.SendAsync("project_update", CurrentProject);
3035
}
3136

3237
var projectText = Configuration["FritzBot:ProjectCommand:TemplateText"];

Fritz.Chatbot/Commands/ShoutoutCommand.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ public class ShoutoutCommand : IBasicCommand2
1515
public ShoutoutCommand(IHttpClientFactory httpClientFactory, ILoggerFactory logger, TwitchTokenConfig twitchConfig = null)
1616
{
1717

18-
_HttpClient = httpClientFactory.CreateClient("ShoutoutCommand");
19-
_HttpClient.BaseAddress = new Uri("https://api.twitch.tv/helix/users");
20-
_HttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {TwitchTokenConfig.Tokens.access_token}");
21-
2218
_Logger = logger.CreateLogger(nameof(ShoutoutCommand));
2319

20+
if (!string.IsNullOrEmpty(TwitchTokenConfig.Tokens?.access_token))
21+
{
22+
_HttpClient = httpClientFactory.CreateClient("ShoutoutCommand");
23+
_HttpClient.BaseAddress = new Uri("https://api.twitch.tv/helix/users");
24+
_HttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {TwitchTokenConfig.Tokens.access_token}");
25+
} else {
26+
_Logger.LogError("Unable to create HttpClient for Twitch with Bearer token");
27+
}
2428
}
2529

2630

Fritz.Chatbot/Commands/ToDoCommand.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Fritz.StreamLib.Core;
2+
using Fritz.StreamTools.Hubs;
3+
using Microsoft.AspNetCore.SignalR;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Fritz.Chatbot.Commands
11+
{
12+
public class ToDoCommand : IBasicCommand2
13+
{
14+
public string Trigger => "todo";
15+
public string Description => "Manage the to-do list displayed. !todo add <text> to add an item, !todo done <id> to mark an item completed, !todo remove <id> to remove an item";
16+
public TimeSpan? Cooldown => TimeSpan.FromSeconds(5);
17+
18+
private static Dictionary<int, (bool completed, string text)> _ToDos = new Dictionary<int, (bool, string)>();
19+
private readonly IHubContext<ObsHub> _HubContext;
20+
21+
public ToDoCommand(IHubContext<ObsHub> hubContext)
22+
{
23+
_HubContext = hubContext;
24+
}
25+
26+
public async Task Execute(IChatService chatService, string userName, bool isModerator, bool isVip, bool isBroadcaster, ReadOnlyMemory<char> rhs)
27+
{
28+
29+
if (!(isBroadcaster || isModerator)) return;
30+
31+
var arrArgs = rhs.ToString().Split(' ');
32+
if (arrArgs[0] == "add") {
33+
var newKey = !_ToDos.Any() ? 1 : _ToDos.Max(t => t.Key) + 1;
34+
_ToDos.Add(newKey, (false, rhs.ToString().Substring(4).Trim()));
35+
await _HubContext.Clients.All.SendAsync("todo_new", newKey, _ToDos[newKey].text);
36+
}
37+
else if (arrArgs[0] == "remove" && int.TryParse(arrArgs[1], out var removeid) && _ToDos.Any(t => t.Key == removeid))
38+
{
39+
_ToDos.Remove(removeid);
40+
await _HubContext.Clients.All.SendAsync("todo_remove", removeid);
41+
}
42+
else if (arrArgs[0] == "done" && int.TryParse(arrArgs[1], out var id) && _ToDos.Any(t => t.Key == id))
43+
{
44+
var todo = _ToDos[id];
45+
todo.completed = true;
46+
_ToDos[id] = todo;
47+
await _HubContext.Clients.All.SendAsync("todo_done", id);
48+
}
49+
else if (arrArgs[0] == "speed" && float.TryParse(arrArgs[1], out var speed))
50+
{
51+
await _HubContext.Clients.All.SendAsync("todo_speed", speed);
52+
}
53+
54+
}
55+
56+
public Task Execute(IChatService chatService, string userName, ReadOnlyMemory<char> rhs)
57+
{
58+
throw new NotImplementedException();
59+
}
60+
61+
public static Dictionary<int, (bool completed, string text)> ToDos { get { return _ToDos; } }
62+
63+
}
64+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@page
2+
@model Fritz.StreamTools.Pages.SentimentModel
3+
@{
4+
}
5+
<!DOCTYPE html>
6+
7+
<html>
8+
<head>
9+
<meta name="viewport" content="width=device-width" />
10+
<title>Sentiment</title>
11+
@*<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/solid.css" integrity="sha384-TbilV5Lbhlwdyc4RuIV/JhD8NR+BfMrvz4BL5QFa2we1hQu6wvREr3v6XSRfCTRp" crossorigin="anonymous">
12+
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/fontawesome.css" integrity="sha384-ozJwkrqb90Oa3ZNb+yKFW2lToAWYdTiF1vt8JiH5ptTGHTGcN7qdoR1F95e0kYyG" crossorigin="anonymous">*@
13+
<link rel="stylesheet" href="~/css/site.css" />
14+
<script src="https://kit.fontawesome.com/8ac2e0bf60.js" crossorigin="anonymous"></script>
15+
</head>
16+
<body>
17+
<div id="sentimentBlock">
18+
<span id="brands" class="toprow">
19+
<i class="fab fa-youtube fa-3x"></i>
20+
<i class="fab fa-github fa-3x"></i>
21+
<i class="fab fa-twitter fa-3x"></i>
22+
</span>
23+
<label class="toprow">csharpfritz</label>
24+
<img class="sentimentGauge" style="z-index: 1;" src="~/img/sentiment-bar-divider.png" />
25+
<div id="constrain">
26+
<div class="ryg-gauge" style="z-index: 0;"></div>
27+
</div>
28+
<label class="bottomrow" style="text-align:left;font-size:16px;top:72px;margin-left:10px">! sentiment for details</label>
29+
<label id="currentSentiment" class="bottomrow">99.9%</label>
30+
</div>
31+
32+
33+
<script src="~/lib/signalr/signalr-client.js"></script>
34+
<script src="~/js/streamhub.js"></script>
35+
<script>
36+
(function () {
37+
38+
var hub = new StreamHub();
39+
var sentimentEl = document.getElementById("currentSentiment");
40+
var gauge = document.getElementById("constrain");
41+
42+
hub.onSentiment = (instant, oneMinute, fiveMinute, all) => {
43+
44+
console.log({
45+
instant: instant,
46+
oneMinute: oneMinute,
47+
fiveMinute: fiveMinute,
48+
all: all
49+
});
50+
51+
gauge.style.width = `${oneMinute * 300}px`;
52+
var trendClass = (fiveMinute < oneMinute) ? "fas fa-arrow-up" : (fiveMinute != oneMinute) ? "fas fa-arrow-down" : "fa fa-arrows-h";
53+
sentimentEl.innerHTML = `<i class="${trendClass}"></i> ${(oneMinute * 100).toFixed(1)}%`;
54+
55+
}
56+
57+
hub.start("sentiment");
58+
59+
})();
60+
</script>
61+
</body >
62+
</html >

Fritz.StreamTools/Pages/ToDo.cshtml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
@page
2+
@{
3+
}
4+
<!DOCTYPE html>
5+
6+
<html>
7+
<head>
8+
<meta name="viewport" content="width=device-width" />
9+
<title>ToDo Widget</title>
10+
<link rel="stylesheet" href="~/css/site.css" />
11+
<script src="https://kit.fontawesome.com/8ac2e0bf60.js" crossorigin="anonymous"></script>
12+
</head>
13+
<body class="todoWidget">
14+
<h4>@Fritz.Chatbot.Commands.ProjectCommand.CurrentProject</h4>
15+
16+
<div id="checklistContainer">
17+
<ul id="todos">
18+
@foreach(var item in Fritz.Chatbot.Commands.ToDoCommand.ToDos) {
19+
var cssClass = item.Value.completed ? "far fa-check-square" : "far fa-square";
20+
<li data-id="@item.Key"><i class="@cssClass"></i> @($"{item.Key}.") @item.Value.text</li>
21+
}
22+
</ul>
23+
</div>
24+
25+
<script src="~/lib/signalr/signalr-client.js"></script>
26+
<script>
27+
28+
var debug = false;
29+
var scrollSpeed = 0.5;
30+
31+
32+
(function () {
33+
34+
this._hub = new signalR.HubConnectionBuilder()
35+
.withUrl("/obshub")
36+
.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
37+
.build();
38+
39+
this._hub.onclose(() => {
40+
if (debug) console.debug("hub connection closed");
41+
42+
// Hub connection was closed for some reason
43+
let interval = setInterval(() => {
44+
// Try to reconnect hub every 5 secs
45+
this.start(groups).then(() => {
46+
// Reconnect succeeded
47+
clearInterval(interval);
48+
if (this.debug) console.debug("hub reconnected");
49+
});
50+
}, 5000);
51+
});
52+
53+
this._hub.on("project_update", (text) => {
54+
if (debug) console.debug(`Project update: ${text}`, null);
55+
document.querySelector("h4").innerText = text;
56+
});
57+
58+
this._hub.on('todo_new', (id, text) => {
59+
if (debug) console.debug("New ToDo", { id, text });
60+
this.Add(id, text);
61+
});
62+
this._hub.on('todo_done', (id) => {
63+
if (this.debug) console.debug("Done ToDo", { id });
64+
this.Complete(id);
65+
});
66+
this._hub.on("todo_remove", (id) => {
67+
if (this.debug) console.debug("Remove ToDo", { id });
68+
this.Remove(id);
69+
});
70+
this._hub.on("todo_speed", (s) => {
71+
if (this.debug) console.debug("ToDo Speed", { s });
72+
scrollSpeed = s;
73+
ConfigureScroll();
74+
});
75+
76+
var todos = document.getElementById("todos");
77+
var todosClone;
78+
var myStylesheet;
79+
var projectTitle = document.querySelector("h4");
80+
var container = document.getElementById("checklistContainer");
81+
82+
this.Add = function (id, text) {
83+
var newEl = document.createElement("li");
84+
newEl.setAttribute("data-id", id);
85+
newEl.innerHTML = `<i class="far fa-square"></i> ${id}. ${text}`;
86+
todos.appendChild(newEl);
87+
ConfigureScroll();
88+
}
89+
90+
this.Complete = function (id) {
91+
var el = document.querySelectorAll(`li[data-id='${id}'] > i`);
92+
el[0].className = "far fa-check-square";
93+
if (el.length > 1) el[1].className = "far fa-check-square";
94+
}
95+
96+
this.Remove = function (id) {
97+
var el = document.querySelectorAll(`li[data-id='${id}'] > i`);
98+
el[0].parentElement.parentElement.removeChild(el[0].parentElement);
99+
if (el.length > 1) el[1].parentElement.parentElement.removeChild(el[1].parentElement);
100+
ConfigureScroll();
101+
}
102+
103+
this.ConfigureScroll = function () {
104+
console.log(`todos: ${todos.scrollHeight} ,project ${projectTitle.offsetHeight}`);
105+
if (todosClone != null) container.removeChild(todosClone);
106+
if (myStylesheet) myStylesheet.sheet.deleteRule(0); // stop scrolling
107+
108+
if (todos.scrollHeight + projectTitle.scrollHeight > 265) {
109+
110+
todos.style.animationName = "scroller";
111+
todos.style.animationDuration = `${(todos.querySelectorAll("li").length * scrollSpeed).toString()}s`;
112+
113+
console.log("Scrolling");
114+
115+
todosClone = todos.cloneNode(true);
116+
todosClone.style.top = `${todos.scrollHeight}px`;
117+
todosClone.id = 'todosClone';
118+
container.appendChild(todosClone);
119+
ConfigureAnimation();
120+
121+
}
122+
};
123+
124+
this.AddKeyFrames = function (name, frames) {
125+
var pos = myStylesheet.length;
126+
myStylesheet.sheet.insertRule(`@@keyframes ${name} {${frames}}`, pos);
127+
};
128+
129+
this.ConfigureAnimation = function () {
130+
131+
if (myStylesheet) {
132+
// myStylesheet.sheet.deleteRule(0);
133+
} else {
134+
myStylesheet = document.createElement("style");
135+
document.head.appendChild(myStylesheet);
136+
}
137+
AddKeyFrames("scroller", `from {transform: translateY(0)} to {transform: translateY(-${todos.scrollHeight}px)}`);
138+
139+
};
140+
141+
ConfigureScroll();
142+
143+
this._hub.start();
144+
145+
})();
146+
</script>
147+
</body>
148+
</html>

Fritz.StreamTools/Views/Followers/Goal.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
8585
(function () {
8686
87-
var hub = new StreamHub();
87+
var hub = new StreamHub();
8888
8989
hub.onFollowers = (data) => {
9090
document.getElementsByClassName("current")[0].textContent = data;

0 commit comments

Comments
 (0)