Skip to content

Commit ee0227c

Browse files
committed
Add Poll Support
1 parent c6fd570 commit ee0227c

7 files changed

Lines changed: 417 additions & 68 deletions

File tree

pages/css/polls.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pages/index.html

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<div class="body-text"> Free Prediction Overlay Features:
3636
<div style="text-align: center;">
3737
<ul style="display: inline-block; text-align: left; vertical-align: top;">
38-
<li>Multi-Option</li>
38+
<li>Multi-Options</li>
3939
<li>Channel Points, User Statistics</li>
4040
<li>XMark, Lock Symbol for Refunded or Locked Prediction</li>
4141
<li>Prediction Persists for 1 Minute after Finish</li>
@@ -48,7 +48,7 @@
4848
<div style="text-align: center;">
4949
<ul style="display: inline-block; text-align: left; vertical-align: top;">
5050
<li>Custom Design in HTML and CSS</li>
51-
<li>Multi-Colored Option</li>
51+
<li>Multi-Colored Options</li>
5252
<li>Animation Options</li>
5353
<li>To get a Custom Design, contact Logicism#9308 on Discord for quotes and pricing</li>
5454
<li>Changes to your design can be changed anytime for free</li>
@@ -58,7 +58,38 @@
5858
</div>
5959
</div>
6060
<div class="body-text">
61-
Get Started: <a class="login-button" href="https://id.twitch.tv/oauth2/authorize?response_type=code&client_id={client_id}&scope=channel:read:predictions&redirect_uri={redirect_uri}"><i class="fa-brands fa-twitch"></i> Login to Twitch</a> <br>
61+
Get Started: <a class="login-button" href="https://id.twitch.tv/oauth2/authorize?response_type=code&client_id={client_id}&scope=channel:read:predictions&redirect_uri={redirect_uri}"><i class="fa-brands fa-twitch"></i> Login to Twitch</a> <br><br>
62+
</div>
63+
<div class="subheader"><a href="/">Poll Overlay</a></div>
64+
<div class="prediction-container">
65+
<div class="img-container"> <img src="img/poll-free-design.png" width="460" height="340" alt=""/>
66+
<div class="body-text"> Free Poll Overlay Features:
67+
<div style="text-align: center;">
68+
<ul style="display: inline-block; text-align: left; vertical-align: top;">
69+
<li>Multi-Choices</li>
70+
<li>Channel Points, User Statistics</li>
71+
<li>Flag Symbol for Finished Prediction</li>
72+
<li>Prediction Persists for 1 Minute after Finish</li>
73+
</ul>
74+
</div>
75+
</div>
76+
</div>
77+
<div class="img-container"> <img src="img/poll-prem-design.png" width="460" height="340" alt=""/>
78+
<div class="body-text"> Custom Poll Overlay Features:
79+
<div style="text-align: center;">
80+
<ul style="display: inline-block; text-align: left; vertical-align: top;">
81+
<li>Custom Design in HTML and CSS</li>
82+
<li>Multi-Colored Choices</li>
83+
<li>Animation Options</li>
84+
<li>To get a Custom Design, contact Logicism#9308 on Discord for quotes and pricing</li>
85+
<li>Changes to your design can be changed anytime for free</li>
86+
</ul>
87+
</div>
88+
</div>
89+
</div>
90+
</div>
91+
<div class="body-text">
92+
Get Started: <a class="login-button" href="https://id.twitch.tv/oauth2/authorize?response_type=code&client_id={client_id}&scope=channel:read:polls&redirect_uri={redirect_uri}"><i class="fa-brands fa-twitch"></i> Login to Twitch</a> <br>
6293
<br>
6394
<br>
6495
<br>

pages/overlay/polls.html

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap" rel="stylesheet">
6+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
7+
<title>Logicism's Twitch Poll Overlay</title>
8+
<link href="css/index.css" rel="stylesheet" type="text/css">
9+
<link href="css/polls.css" rel="stylesheet" type="text/css">
10+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
11+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
12+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
13+
<link rel="manifest" href="/site.webmanifest">
14+
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
15+
<meta name="msapplication-TileColor" content="#da532c">
16+
<meta name="theme-color" content="#0084ff">
17+
<meta property="og:type" content="website">
18+
<meta property="og:title" content="Logicism's Twitch Overlays">
19+
<meta property="og:url" content="https://overlay.logicism.tv/">
20+
<meta property="og:image" content="https://logicism.tv/img/card-banner.png">
21+
<meta property="og:description" content="The Official Website of Live Streamer, Content Creator, Programmer and Graphic Designer Logicism!">
22+
<meta property="twitter:card" content="summary_large_image">
23+
<meta property="twitter:domain" content="overlay.logicism.tv">
24+
<meta property="twitter:url" content="https://overlay.logicism.tv/">
25+
<meta property="twitter:title" content="Logicism's Twitch Overlays">
26+
<meta property="twitter:description" content="Logicism's Twitch Overlays Programmed in HTML, CSS, Javascript with Twitch's API">
27+
<meta property="twitter:image" content="https://logicism.tv/img/card-banner.png">
28+
</head>
29+
<body>
30+
<div class="poll-overlay" style="display: none;">
31+
<div class="poll-title">
32+
<div class="poll-time-text" style="display: none;">00:00 </div>
33+
<div class="poll-title-text">STREAM TITLE</div>
34+
</div>
35+
<div class="poll-children"> </div>
36+
</div>
37+
<script>
38+
const openSocket = () => {
39+
let countdownTimerId = 0;
40+
let timeoutId = 0;
41+
sock = new WebSocket("wss://ws.logicism.tv");
42+
43+
sock.addEventListener("open", e => {
44+
console.log("Connected to WebSocket Server");
45+
});
46+
47+
sock.addEventListener("message", e => {
48+
const messageObj = JSON.parse(e.data);
49+
50+
if (messageObj.subscription != undefined) {
51+
if (messageObj.subscription.type == "channel.poll.begin") {
52+
if (timeoutId != 0) {
53+
clearTimeout(timeoutId);
54+
timeoutId = 0;
55+
}
56+
57+
if (countdownTimerId != 0) {
58+
clearInterval(countdownTimerId);
59+
countdownTimerId = 0;
60+
}
61+
62+
document.getElementsByClassName("poll-children")[0].innerHTML = "";
63+
document.getElementsByClassName("poll-title-text")[0].innerHTML = messageObj.event.title;
64+
65+
let totalChannelPoints = 0;
66+
67+
for (let i = 0; i < messageObj.event.choices.length; i++) {
68+
totalChannelPoints += messageObj.event.choices[i].votes;
69+
}
70+
71+
for (let i = 0; i < messageObj.event.choices.length; i++) {
72+
document.getElementsByClassName("poll-children")[0].innerHTML +=
73+
'<progress<div class="poll-child ' + messageObj.event.choices[i].id + '"> <div class="progress-bar-stats"> <div class="progress-bar-stats-child"> <div class="progress-bar-users"><span class="progress-bar-users-text"> 0 </span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16"> <path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/> </svg> </div> </div> <div class="progress-bar-stats-child"> <div class="progress-bar-text">' + messageObj.event.choices[i].title + '</div> </div> </div> <div class="progress-bar"> <div class="poll-outcome progress-bar-foreground-1' + '" style="width: 0%; content: 0%;"></div> </div> </div>';
74+
}
75+
76+
let timeToLock = new Date(messageObj.event.ends_at);
77+
document.getElementsByClassName("poll-time-text")[0].innerHTML = "00:00 ";
78+
document.getElementsByClassName("poll-time-text")[0].setAttribute("style", "display: inline-block;");
79+
80+
countdownTimerId = setInterval(function () {
81+
let now = new Date().getTime();
82+
let timeleft = timeToLock - now;
83+
84+
if (timeleft > 0) {
85+
let minutes = Math.floor((timeleft % (1000 * 60 * 60)) / (1000 * 60));
86+
let seconds = Math.floor((timeleft % (1000 * 60)) / 1000);
87+
88+
document.getElementsByClassName("poll-time-text")[0].innerHTML = String(minutes).padStart(2, '0') + ":" + String(seconds).padStart(2, '0') + " ";
89+
}
90+
}, 1000);
91+
92+
93+
if (document.getElementsByClassName("poll-overlay")[0].getAttribute("style") != "display: inherit;") {
94+
document.getElementsByClassName("poll-overlay")[0].setAttribute("style", "display: inherit;");
95+
}
96+
97+
console.log("Displaying poll Overlay");
98+
} else if (messageObj.subscription.type == "channel.poll.progress") {
99+
let totalChannelPoints = 0;
100+
101+
for (let i = 0; i < messageObj.event.choices.length; i++) {
102+
totalChannelPoints += messageObj.event.choices[i].votes;
103+
}
104+
105+
for (let i = 0; i < messageObj.event.choices.length; i++) {
106+
document.getElementsByClassName(messageObj.event.choices[i].id)[0].getElementsByClassName("poll-outcome")[0].setAttribute("style", "width:" + ((messageObj.event.choices[i].votes / totalChannelPoints) * 100) + "%;");
107+
document.getElementsByClassName(messageObj.event.choices[i].id)[0].getElementsByClassName("progress-bar-users-text")[0].innerHTML = " " + messageObj.event.choices[i].votes.toLocaleString("en-US") + " ";
108+
}
109+
110+
console.log("Updating poll Overlay");
111+
} else if (messageObj.subscription.type == "channel.poll.end") {
112+
if (countdownTimerId != 0) {
113+
clearInterval(countdownTimerId);
114+
countdownTimerId = 0;
115+
116+
document.getElementsByClassName("poll-time-text")[0].innerHTML = "00:00 ";
117+
document.getElementsByClassName("poll-time-text")[0].setAttribute("style", "display: none;");
118+
}
119+
120+
if (messageObj.event.status == "terminated" || messageObj.event.status == "completed") {
121+
document.getElementsByClassName("poll-title-text")[0].innerHTML = '<i class="fa-solid fa-flag-checkered"></i> ' + messageObj.event.title;
122+
123+
console.log("Displaying Flag Symbol");
124+
} else {
125+
document.getElementsByClassName("poll-title-text")[0].innerHTML = '<i class="fa-solid fa-circle-xmark"></i> ' + messageObj.event.title;
126+
127+
console.log("Displaying XMark Symbol");
128+
}
129+
130+
timeoutId = setTimeout(function () {
131+
document.getElementsByClassName("poll-overlay")[0].setAttribute("style", "display: none;");
132+
133+
console.log("Removing poll Overlay");
134+
135+
timeoutId = 0;
136+
}, 60000);
137+
}
138+
} else {
139+
if (messageObj.type == "welcome") {
140+
sock.send(JSON.stringify({
141+
session_id: messageObj.session_id,
142+
type: "welcome",
143+
user_id: "{user_id}",
144+
scope: "channel:read:polls",
145+
user_agent: navigator.userAgent
146+
}));
147+
} else if (messageObj.type == "channel.poll.begin") {
148+
document.getElementsByClassName("poll-title-text")[0].innerHTML = messageObj.title;
149+
150+
let totalChannelPoints = 0;
151+
152+
for (let i = 0; i < messageObj.choices.length; i++) {
153+
totalChannelPoints += messageObj.choices[i].votes;
154+
}
155+
156+
for (let i = 0; i < messageObj.choices.length; i++) {
157+
document.getElementsByClassName("poll-children")[0].innerHTML +=
158+
'<div class="poll-child ' + messageObj.choices[i].id + '"> <div class="progress-bar-stats"> <div class="progress-bar-stats-child"> <div class="progress-bar-users"><span class="progress-bar-users-text"> ' + messageObj.choices[i].votes.toLocaleString("en-US") + ' </span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16"> <path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/> </svg> </div> </div> <div class="progress-bar-stats-child"> <div class="progress-bar-text">' + messageObj.choices[i].title + '</div> </div> </div> <div class="progress-bar"> <div class="poll-outcome progress-bar-foreground-1" style="width: ' + (totalChannelPoints != 0 ? Math.round((messageObj.choices[i].votes / totalChannelPoints) * 100) + '%; content: ' + Math.round((messageObj.choices[i].votes / totalChannelPoints) * 100) : '0%; content: 0%;') + '%"></div> </div> </div>';
159+
}
160+
161+
document.getElementsByClassName("poll-overlay")[0].setAttribute("style", "display: inherit;");
162+
163+
console.log("Displaying poll Overlay");
164+
}
165+
}
166+
});
167+
168+
sock.addEventListener("close", e => {
169+
document.getElementsByClassName("poll-overlay")[0].setAttribute("class", "poll-overlay");
170+
document.getElementsByClassName("poll-overlay")[0].setAttribute("style", "display: none;");
171+
document.getElementsByClassName("poll-children")[0].innerHTML = "";
172+
173+
174+
setTimeout(function () {
175+
console.log("Reconnecting to WebSocket Server in 3 seconds");
176+
177+
openSocket();
178+
}, 3000)
179+
});
180+
};
181+
182+
openSocket();
183+
</script>
184+
</body>
185+
</html>

src/me/Logicism/TwitchOverlayServer/http/handlers/OverlayHandler.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@ public void handle(HttpExchange exchange) throws IOException {
3939
} else {
4040
HTTPUtils.throwError(exchange, "URL does not have id query!");
4141
}
42+
} else if (exchange.getRequestURI().toString().startsWith("/overlay/polls")) {
43+
Map<String, String> queryMap = TextUtils.queryToMap(exchange.getRequestURI().getQuery());
44+
if (queryMap.containsKey("id")) {
45+
if (TwitchOverlayServer.INSTANCE.containsUserTokenHandle(queryMap.get("id"),
46+
"channel:read:polls")) {
47+
if (HTTPUtils.containsFile("/overlay/polls-premium/" + queryMap.get("id") + ".html")) {
48+
HTTPUtils.throwSuccessPage(exchange, HTTPUtils.getFile("/overlay/polls-premium/" + queryMap.get("id") + ".html"));
49+
} else {
50+
String response = FileUtils.fileToString(HTTPUtils.getFile("/overlay/polls.html"))
51+
.replace("{user_id}", queryMap.get("id"));
52+
53+
HTTPUtils.throwSuccessHTML(exchange, response);
54+
}
55+
} else {
56+
HTTPUtils.throwError(exchange, "User is not authenticated! Please reauthenticate and refresh!");
57+
}
58+
} else {
59+
HTTPUtils.throwError(exchange, "URL does not have id query!");
60+
}
4261
} else {
4362
HTTPUtils.throwError(exchange, "Cannot find that overlay!");
4463
}

0 commit comments

Comments
 (0)