49 Commits
Author SHA1 Message Date
sugoidogo 7fdadbe93f css update
Publish / release (push) Successful in 37s
2026-07-29 01:53:36 -07:00
sugoidogo 4ca7c28e77 fix import
Publish / release (push) Successful in 1m1s
2025-12-17 02:56:16 -08:00
sugoidogo a3e69d4b68 compile before test 2025-12-17 02:56:10 -08:00
sugoidogo 809d3ed5a2 refactor
Publish / release (push) Failing after 46s
2025-12-17 02:53:36 -08:00
sugoidogo a8a7ec7109 add loading message
Publish / release (push) Successful in 2m11s
2025-12-16 03:22:43 -08:00
sugoidogo 12dc1b886f add test command 2025-12-16 03:22:36 -08:00
sugoidogo c835c6514e update to fixed webstorage
Publish / release (push) Successful in 2m4s
2025-12-16 03:02:31 -08:00
sugoidogo d56a11c482 add origin param to script 2025-12-16 03:02:13 -08:00
sugoidogo d1abbca707 update gitignore 2025-12-16 03:01:39 -08:00
sugoidogo 4fc82f50f5 fix wrangler confusion
Publish / release (push) Successful in 2m3s
2025-12-16 02:36:17 -08:00
sugoidogo be87605e43 update ci
Publish / release (push) Successful in 2m21s
2025-12-16 02:19:01 -08:00
sugoidogo f699248865 update readme 2025-12-16 02:11:01 -08:00
sugoidogo 9c07d2345d update to ts and cloudflare pages
Publish / release (push) Successful in 2m13s
2025-12-16 02:01:08 -08:00
sugoidogo b5779bfd70 fix bit event 2025-07-27 02:29:27 -07:00
sugoidogo 46c92654bc fix moderator list 2025-07-27 02:03:54 -07:00
sugoidogo 0a5191063d fix conditional streamelements init 2025-02-09 04:18:35 +00:00
sugoidogo 7cd791b7df remove unused variables and single use functions 2025-02-09 04:16:09 +00:00
sugoidogo 3f1fe62926 remove unused functions 2025-02-09 04:04:04 +00:00
sugoidogo 343ea2e2e9 pubsub depreciation 2025-02-09 04:01:11 +00:00
SugoiDogo f4a7b57968 Create static.yml 2025-02-08 12:05:22 -08:00
SugoiDogo 7b10257261 Delete .github/workflows/static.yml 2025-02-08 12:05:06 -08:00
SugoiDogo 7912055962 Update static.yml 2025-02-08 12:03:01 -08:00
sugoidogo 20bc1a89c8 prepare for pubsub depreciation, switch to ebs services 2025-02-08 19:49:03 +00:00
SugoiDogo 81fc3daf42 disable debug message 2024-12-24 16:22:41 -08:00
josephsmendoza 2d5ec72582 add release info to error tracking 2024-11-08 03:25:39 -08:00
SugoiDogo b7108f729b Update style.css 2024-10-10 05:31:11 -07:00
SugoiDogo 625fd31551 Update style.css 2024-10-10 05:23:39 -07:00
josephsmendoza 3748956e5d add hostname to sentry init 2024-10-01 00:47:50 -07:00
josephsmendoza 2f8bcbb938 add debugging tools 2024-09-30 09:43:30 -07:00
josephsmendoza 8f30c52f47 fix bit message handling 2024-01-10 05:30:31 -08:00
josephsmendoza e2eafaf3f5 add chat:edit permission 2023-11-30 12:11:35 -08:00
SugoiDogo 3dd7d7fedc Create static.yml 2023-11-30 12:04:35 -08:00
josephsmendoza 3f98d9483e add a connection message 2023-11-30 12:03:35 -08:00
josephsmendoza 27cfcc800a don't trigger reconnect on reconnect 2023-11-30 12:00:58 -08:00
josephsmendoza 0300183adc reconnect to irc on errors 2023-11-30 11:52:00 -08:00
josephsmendoza 28cc128605 reverse change 2023-11-29 18:46:28 -08:00
josephsmendoza 8aabc03604 fix parsing error 2023-11-29 18:43:50 -08:00
josephsmendoza 91141fc5f6 fix typo 2023-11-29 18:42:13 -08:00
josephsmendoza ffb6a97bfe fix sub events 2023-11-29 18:37:30 -08:00
josephsmendoza 9de8cc30bc fix pubsub message handling 2023-08-25 19:00:54 -07:00
josephsmendoza 22070384b4 enable all events by default 2023-08-25 18:18:55 -07:00
josephsmendoza 9a48bf8b2b update readme, fix incorrect label 2023-08-25 12:44:23 -07:00
josephsmendoza fa4ce9608a streamelements tip support 2023-08-25 12:36:12 -07:00
josephsmendoza fd41eead59 why the fuck is there a json string nested in the json 2023-07-20 16:17:38 -07:00
josephsmendoza dd94e42851 allow decimal values 2023-07-20 16:17:11 -07:00
josephsmendoza 907cbea17a prevent bugs caused by calling functions out of order 2023-07-14 20:15:33 -07:00
josephsmendoza 7d71419125 set time_passed on reset() 2023-07-14 19:13:40 -07:00
josephsmendoza d72a2bd2f5 add config.html 2023-07-14 19:07:57 -07:00
josephsmendoza aca8b21945 convert to sugoi web services 2023-07-14 19:05:49 -07:00
27 changed files with 3066 additions and 1240 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Publish
on:
push:
tags:
- "*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: latest
- run: npm ci
- run: npm run publish
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+3
View File
@@ -0,0 +1,3 @@
node_modules
dist/js
.wrangler
+1
View File
@@ -0,0 +1 @@
@sugoidogo:registry=https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/
+20 -58
View File
@@ -1,65 +1,27 @@
<h1 align="center">Improved Twitch Subathon Countdown</h1> # Improved Twitch Subathon Countdown
<p align="center">This is a Twitch Subathon Countdown originally by JayexDesigns.</p> This is a Twitch Subathon Countdown originally by JayexDesigns.
<p align="center">The CSS Code is updated by Johnnycyan. The JS Files are updated by leabdd and danilotitato to add functionalities.</p>
<br/>
<h2>Description</h2> The CSS Code is updated by Johnnycyan.
<p>A subathon timer that increases when someone subscribes, donates money, donates bits or purchases streamloots chests.</p>
<p>Added is a Timer that gets saved (in localStorage) and can be reset.</p>
<p>When you open the index.html you get to chose between "Reset" or "Start".</p>
<br/>
<h2>Shortcuts</h2> The JS Files have been re-written by SugoiDogo.
<p>To pause the Timer: CTRL+ALT+P</p>
<p>To enable/disable Happy Hour: CTRL+ALT+H</p>
<p>To enable/disable Random Hour: CTRL+ALT+R</p>
<p>Shortcuts can be changed in config.js</p>
<br/>
<h2>Preview</h2> ## Description
<h3 align="center"><img width="90%" src="./preview.webp"></h3> A subathon timer that increases when someone subscribes, donates to your charity, donates bits, follows or raids.
<br/>
<h2>Usage</h2> ## Usage
<p>After cloning this repository you'll have to edit the "config.js" file with any plain text editor, then change the value of the variables according to what you want:</p> Go to the [config page](https://subathon.sugoidogo.com/config.html), authorize the timer, and configure to your liking.
<h3>Twitch</h3> ### StreamElements Tips
<p>To get your Subs and Bits you just have to add your Channel Name.</p> You will need your StreamElements Overlay Token to use this feature.
You can get if from your [StreamElements channel settings](https://streamelements.com/dashboard/account/channels).
<h3>Streamlabs Token</h3> ![StreamElements Overlay Token Image](auth_streamelements.png)
<p>To get the token you'll have to go to the API settings tab of your <a href="https://streamlabs.com/dashboard#/settings/api-settings">Streamlabs dashboard</a> then click on API Tokens, copy your socket API token and finally paste the code on the "streamlabs_token" variable in the "config.js" file.</p>
<img src="./auth_streamlabs.png">
<h3>StreamElements Token</h3> ## Missing Features
<p>To get the token you'll have to go to your <a href="https://streamelements.com/dashboard/account/channels">StreamElements channels dashboard</a>, then click on show secrets, copy the Overlay token and paste the code on the "streamelements_token" variable in the "config.js" file.</p> The original version of this overlay included support for events from StreamLabs and Streamloots,
<img src="./auth_streamelements.png"> and the version this is forked from included additional features by leabdd and danilotitato,
such as Random Happy Hour and Bulk Events.
<h3>Streamloots Token</h3> This fork is an almost total re-write of the core JS code behind the timer,
<p>To get the token you'll have to go to your Streamloots creator dashboard, then to alerts & widgets, then go to alerts, then copy the last part of the alerts url and finally paste the code on the "streamloots_token" variable in the "config.js" file.</p> so those features will also have to be re-written to be included in this fork.
<img src="./auth_streamloots.png"> Future versions of this timer will include a plug-in loader,
allowing additional features and event sources to be added without additional forking.
<h3>Initial Time</h3>
<p>You can set the initial time of the timer by setting the hours, minutes and seconds to be added when the timer is created.</p>
<h3>Random Happy Hour</h3>
<p>If this is true, you can turn enable Happy or Random Hour with the Shortcut. If it is false, the feature is disabled and cant be enabled.</p>
<p>You have to set the Happy Hour time values manually.</p>
<p>Random Hour has a bell curve which can be edited in the config. It multiplies the normal time by a random amount for each sub.</p>
<p>If enabled randHappy it can randomly activate the Happy Hour. It will last for one hour, but can be turned off manually with the Shortkey.</p>
<p>In the Config you can set a specific Date and Time. At that a Happy Hour will happen. scheduleHappy needs to be turned on.</p>
<h3>Bulk</h3>
<p>This enables that multiple Subs get added to the timer at once.</p>
<p>For Example when a user gifts 10 Subs.</p>
<h3>Full control on what to use</h3>
<p>You can enable and disable if you want to add time for Subscriptions, Bits, Donations or Chests.</p>
<h3>Other Values</h3>
<p>Change the other values to set the amount of seconds that will be added for the subscriptions, donations... And the minimum donation amounts to trigger the countdown increase.</p>
<h3>Add To OBS</h3>
<p>To add it to OBS you just have to drag the "index.html" file to the sources section or add a browser source that points to the "index.html" file.</p>
<h2>Known Problems</h2>
<p2>Only Streamelements Token in config.js does not add time for Subscriptions. Emulate with the Overlay Editor does work.</p>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

-80
View File
@@ -1,80 +0,0 @@
// Happy Hour
var happy_hour = true // set to true or false, to enable/disable the happy hours function
var random_hour = true
var randHappy = false // only works if happy_hour = true
var scheduleHappy = false // only works if happy_hour = true
// needs scheduleHappy to be true
var scheduleHappyDay = 5 // Weekdays from 0 to 6 from Sunday to Saturday | Default 5 is Friday
var scheduleHappyHour = 18 // Hours in 24 format UTC Time | Default 18 is 20:00 CEST
// Bulk (if you want multiple subs to add to a big sum or each alone)
var bulk_enabled = false
//Shortcuts
var pauseShort = "ctrl+alt+p"
var happyHourShort = "ctrl+alt+h"
var randomHourShort = "ctrl+alt+r"
// Login Data
var twitch_channel_name = ""
var streamlabs_token = ""
var streamelements_token = ""
var streamloots_token = ""
// Initial Counter Config
var initialHoursConfig = 2
var initialMinutesConfig = 0
var initialSecondsConfig = 0
// Sync Time, amount of seconds it waits after the timer hit 0 to stop it. Useful for last Second Subs.
var syncTime = 2 // Default 2 seconds.
// Enable/Disable Subs, Bits, Donations, Chests
var subEnable = true
var bitEnable = false
var donationEnable = false
var chestEnable = false
// General Twitch, Streamlabs And StreamElements Config
var seconds_added_per_sub_prime = 60
var seconds_added_per_sub_tier1 = 60
var seconds_added_per_sub_tier2 = 180
var seconds_added_per_sub_tier3 = 480
var seconds_added_per_resub_prime = 60
var seconds_added_per_resub_tier1 = 60
var seconds_added_per_resub_tier2 = 180
var seconds_added_per_resub_tier3 = 480
var seconds_added_per_giftsub_tier1 = 60
var seconds_added_per_giftsub_tier2 = 180
var seconds_added_per_giftsub_tier3 = 480
var min_amount_of_bits = 50
var seconds_added_per_bits = 30
// Streamlabs And StreamElements Config
var min_donation_amount = 1
var seconds_added_per_donation = 30
// Streamloots Config
var min_amount_of_chests = 1
var seconds_added_per_chests = 30
// General Twitch, Streamlabs And StreamElements Config for Happy Hour
var factor_t1 = 1.5
var factor_t2 = 1.75
var factor_t3 = 2.0
var factor_bits = 1.5
var factor_donations = 1.5
// General Twitch, Streamlabs And StreamElements Config for Random Hour
// [min, max, skew]
var range_t1 = [0.1, 3, 1]
var range_t2 = [0.1, 3, 2]
var range_t3 = [0.1, 3, 3]
var range_bits = [0.1, 3, 1]
var range_donations = [0.1, 3, 1]
-229
View File
@@ -1,229 +0,0 @@
* {
font-family: 'Ubuntu', sans-serif !important;
}
body, body * {
padding: 0;
margin: 0;
font-family: 'Manrope', sans-serif;
}
body {
width: 100vw;
height: 100vh;
}
:root {
--background-color: #A020F0;
--shadow-color: #00000090;
--text-color: #FFFFFF;
}
#container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/*background-color: var(--background-color);*/
background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0,0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0074cc, #a700cc);
box-shadow: 0 0 1.25rem 0.5rem var(--shadow-color);
width: 500px;
height: 180px;
border-radius: 30px;
visibility: hidden;
}
#SpecialHourText {
opacity: 0;
font-size: 2.5rem;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
/* #HappyHourDeactive {
visibility: hidden;
font-size: 3rem;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
#HappyHourError {
visibility: hidden;
font-size: 3rem;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
} */
#SpecialHourHTML {
top:-200px;
position: relative;
}
#startPage {
position: absolute;
align-items: center;
top: 75%;
left: 50%;
transform: translate(-50%, -50%);
/*background-color: var(--background-color);*/
width: 150px;
height: 50px;
border-radius: 30px;
background:none;
}
#textDiv {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.button {
border-radius: 30px;
box-shadow: 0 0 1.25rem 0.5rem var(--shadow-color);
justify-content: center;
align-items: center;
display: flex;
height: 100%;
width: 150px;
height: 50px;
top: 50%;
left: 50%;
color:white;
/*background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0,0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0074cc, #a700cc);*/
background:none;
}
#firstButtonDiv, #secondButtonDiv {
background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0,0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0074cc, #a700cc);
width: 150px;
height: 50px;
display: inline-block;
}
#firstButtonDiv:hover, #secondButtonDiv:hover {
background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0,0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0061ac, #8e00ad)!important;
}
#firstButtonDiv, #secondButtonDiv {
border-radius: 30px;
justify-content: center;
align-items: center;
display: flex;
height: 100%;
width: 150px;
height: 50px;
top: 50%;
left: 50%;
color:white;
background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0,0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0074cc, #a700cc);
}
#secondButtonDiv {
margin-top:10px;
}
p {
color: var(--text-color);
font-size: 5.8rem;
z-index: 1;
text-shadow: 0px 0px 10px var(--shadow-color);
}
img {
position: absolute;
top: 0;
bottom: 0;
}
canvas {
width: 500px;
height: 180px;
border-radius: 20px;
position: absolute;
top: 0;
bottom: 0;
z-index: 2;
}
.addedTime {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
display: none;
z-index: 5;
font-weight: 400;
font-size: 3.25rem;
/*color: var(--background-color);*/
color:white;
transition: top 1s ease-out, opacity .5s ease-out;
}
.gold {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
display: none;
z-index: 5;
font-weight: 400;
font-size: 3.25rem;
/*color: var(--background-color);*/
color:gold;
background: linear-gradient(to right, #BF953F, #FCF6BA, #B38728, #FBF5B7, #AA771C);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
transition: top 1s ease-out, opacity .5s ease-out;
text-shadow:none !important;
}
+79
View File
@@ -0,0 +1,79 @@
* {
font-family: 'Ubuntu', sans-serif !important;
}
body,
body * {
padding: 0;
margin: 0;
font-family: 'Manrope', sans-serif;
}
body {
width: 100vw;
height: 100vh;
}
:root {
--background-color: #A020F0;
--shadow-color: #00000090;
--text-color: #FFFFFF;
}
#container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/*background-color: var(--background-color);*/
background-image:
-webkit-linear-gradient(-45deg,
transparent 33%, rgba(0, 0, 0, .1) 33%,
rgba(0, 0, 0, .1) 66%, transparent 66%),
-webkit-linear-gradient(top,
rgba(255, 255, 255, .25),
rgba(0, 0, 0, .25)),
-webkit-linear-gradient(left, #0074cc, #a700cc);
box-shadow: 0 0 1.25rem 0.5rem var(--shadow-color);
width: 500px;
height: 180px;
border-radius: 30px;
}
#textDiv {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
p {
color: var(--text-color);
font-size: 5.8rem;
z-index: 1;
text-shadow: 0px 0px 10px var(--shadow-color);
}
.addedTime {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
font-weight: 400;
font-size: 3.25rem;
color: white;
animation: slidefadeout 2s ease-out;
opacity: 0;
}
@keyframes slidefadeout {
0% {
opacity: 1;
top: 50%;
}
100% {
opacity: 0;
top: 25%;
}
}
+106
View File
@@ -0,0 +1,106 @@
<head>
<link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<h1>Sugoi Streamathon Timer</h1>
<div id="status">Loading...</div>
<br>
<form id="config">
<label>
Starting Time:
<input type="text" name="start-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="8:00:00" required>
</label>
<br>
<label>
<input type="checkbox" name="max-time-enabled" checked>
Maximum Time:
<input type="text" name="max-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="12:00:00" required>
</label>
<br>
<label>
<input type="checkbox" name="sub1000-time-enabled" checked>
Time Per T1 Sub:
<input type="text" name="sub1000-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="500" required>
</label>
<br>
<label>
<input type="checkbox" name="sub2000-time-enabled" checked>
Time Per T2 Sub:
<input type="text" name="sub2000-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="1000" required>
</label>
<br>
<label>
<input type="checkbox" name="sub3000-time-enabled" checked>
Time Per T3 Sub:
<input type="text" name="sub3000-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="2500" required>
</label>
<br>
<label>
<input type="checkbox" name="bit-time-enabled" checked>
Time Per Bit:
<input type="text" name="bit-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="1" required>
</label>
<br>
<label>
<input type="checkbox" name="follow-time-enabled" checked>
Time Per Follow:
<input type="text" name="follow-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="1:00" required>
</label>
<br>
<label>
<input type="checkbox" name="raid-time-enabled" checked>
Time Per Raid:
<input type="text" name="raid-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="10:00" required>
</label>
<br>
<label>
<input type="checkbox" name="charity-time-enabled" checked>
Time Per Charity Cent:
<input type="text" name="charity-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="1" required>
</label>
<br>
<br>
<label>
StreamElements Overlay Token:
<input type="text" name="streamelements-token">
</label>
<br>
<label>
<input type="checkbox" name="tip-time-enabled" checked>
Time Per Tip Cent:
<input type="text" name="tip-time" pattern="[\d.\d:]+" placeholder="HH:MM:SS" value="1">
</label>
<br>
<br>
<button type="submit">Save</button>
<button type="reset">Defaults</button>
<a id="timer-link" href="timer.html" target="_blank">Browser Source</a>
<div>
<h3>Chat Commands for Moderators:</h3>
!subathon start <br>
!subathon pause <br>
!subathon add 1:0:0 <br>
!subathon reset
</div>
</form>
<style>
* {
font-family: sans-serif;
}
::placeholder {
color: lightgray;
}
fieldset {
border: none;
margin: 0;
padding: 0;
}
iframe {
overflow: hidden;
}
</style>
<script type="module" src="js/config.js"></script>
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
<title>Subathon Timer</title>
</head>
<body>
<div id="container" class="timer">
<div id="textDiv">
<p id="timeText">Loading...</p>
</div>
</div>
<script type="module" src="js/script.js"></script>
</body>
</html>
-51
View File
@@ -1,51 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
<title>Subathon Timer</title>
</head>
<body>
<div id="startPage">
<div id="firstButtonDiv">
<button id="resetBtn" class="button">Reset</button>
</div>
<div id="secondButtonDiv">
<button id="startBtn" class="button">Start</button>
</div>
</div>
<div id="container" class="timer">
<div id="textDiv">
<p id="timeText">00:00:00</p>
</div>
<div id="SpecialHourHTML">
<p id="SpecialHourText">Happy Hour Activated!</p>
<!-- <p id="SpecialHourDeactive">Happy Hour Deactivated :(</p>
<p id="SpecialHourError">Happy Hour not available D:</p> -->
</div>
<canvas id="canvas"></canvas>
</div>
<script src="js/lib/mousetrap.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js' type='text/javascript'></script>
<script src='js/lib/tmi.min.js'></script>
<script src="js/lib/generalFunc.js"></script>
<script src="js/lib/timeFunc.js"></script>
<script src="config.js"></script>
<script src="js/script.js"></script>
<script src="js/api/twitch.js"></script>
<!-- <script src="js/api/streamlabs.js"></script> -->
<script src="js/api/streamelements.js"></script>
<!-- <script src="js/api/streamloots.js"></script> -->
</body>
</html>
-128
View File
@@ -1,128 +0,0 @@
if (streamelements_token !== "") {
var streamElementsSocket;
const streamElementsConnect = () => {
streamElementsSocket = undefined;
streamElementsSocket = io('https://realtime.streamelements.com', {
transports: ['websocket']
});
streamElementsSocket.on('connect', onConnect);
streamElementsSocket.on('disconnect', onDisconnect);
streamElementsSocket.on('authenticated', onAuthenticated);
streamElementsSocket.on('unauthorized', console.error);
streamElementsSocket.on('event:test', onEvent);
streamElementsSocket.on('event', onEvent);
streamElementsSocket.on('event:update', onEvent);
streamElementsSocket.on('event:reset', onEvent);
function onConnect() {
logMessage("StreamElements", "Socket Connected");
streamElementsSocket.emit('authenticate', {method: 'apikey', token: streamelements_token});
}
function onDisconnect() {
logMessage("StreamElements", "Socket Disconnected");
streamElementsConnect();
}
function onAuthenticated(data) {
const {
channelId
} = data;
logMessage("StreamElements", `Channel ${channelId} Connected`);
}
function onEvent(data) {
logObject("StreamElements", data);
if (!countdownEnded) {
let factor_t1_local = (happy_hour_active ? factor_t1 : 1) * (random_hour_active ? randomBellSkew(...range_t1): 1);
let factor_t2_local = (happy_hour_active ? factor_t2 : 1) * (random_hour_active ? randomBellSkew(...range_t2): 1);
let factor_t3_local = (happy_hour_active ? factor_t3 : 1) * (random_hour_active ? randomBellSkew(...range_t3): 1);
let factor_bits_local = (happy_hour_active ? factor_bits : 1) * (random_hour_active ? randomBellSkew(...range_bits): 1);
let factor_donations_local = (happy_hour_active ? factor_donations : 1) * (random_hour_active ? randomBellSkew(...range_donations): 1);
if ((data['listener'] === "subscriber-latest") && subEnable) {
if (data['event']['gifted'] || data['event']['bulkGifted']) {
let amount;
if (data['event']['gifted']) amount = 1;
else amount = data['event']['amount'];
if (data['event']['tier'] == "prime" || data['event']['tier'] == "1000") {
addTime(endingTime, seconds_added_per_giftsub_tier1 * amount * factor_t1_local);
logMessage("StreamElements", `Added ${seconds_added_per_giftsub_tier1 * amount * factor_t1_local} Seconds Because ${data['event']['name']} Gifted ${amount} Tier 1 Subs`);
}
else if (data['event']['tier'] == "2000") {
addTime(endingTime, seconds_added_per_giftsub_tier2 * amount * factor_t2_local);
logMessage("StreamElements", `Added ${seconds_added_per_giftsub_tier2 * amount * factor_t2_local} Seconds Because ${data['event']['name']} Gifted ${amount} Tier 2 Subs`);
}
else if (data['event']['tier'] == "3000") {
addTime(endingTime, seconds_added_per_giftsub_tier3 * amount * factor_t3_local);
logMessage("StreamElements", `Added ${seconds_added_per_giftsub_tier3 * amount * factor_t3_local} Seconds Because ${data['event']['name']} Gifted ${amount} Tier 3 Subs`);
}
}
else if (data['event']['amount'] != "1") {
if (data['event']['tier'] == "prime") {
addTime(endingTime, seconds_added_per_resub_prime * factor_t1_local);
logMessage("StreamElements", `Added ${seconds_added_per_resub_prime * factor_t1_local} Seconds Because ${data['event']['name']} ReSubscribed With Prime`);
}
else if (data['event']['tier'] == "1000") {
addTime(endingTime, seconds_added_per_resub_tier1 * factor_t1_local);
logMessage("StreamElements", `Added ${seconds_added_per_resub_tier1 * factor_t1_local} Seconds Because ${data['event']['name']} ReSubscribed With Tier 1`);
}
else if (data['event']['tier'] == "2000") {
addTime(endingTime, seconds_added_per_resub_tier2 * factor_t2_local);
logMessage("StreamElements", `Added ${seconds_added_per_resub_tier2 * factor_t2_local} Seconds Because ${data['event']['name']} ReSubscribed With Tier 2`);
}
else if (data['event']['tier'] == "3000") {
addTime(endingTime, seconds_added_per_resub_tier3 * factor_t3_local);
logMessage("StreamElements", `Added ${seconds_added_per_resub_tier3 * factor_t3_local} Seconds Because ${data['event']['name']} ReSubscribed With Tier 3`);
}
}
else {
if (data['event']['tier'] == "prime") {
addTime(endingTime, seconds_added_per_sub_prime * factor_t1_local);
logMessage("StreamElements", `Added ${seconds_added_per_sub_prime * factor_t1_local} Seconds Because ${data['event']['name']} Subscribed With Prime`);
}
else if (data['event']['tier'] == "1000") {
addTime(endingTime, seconds_added_per_sub_tier1 * factor_t1_local);
logMessage("StreamElements", `Added ${seconds_added_per_sub_tier1 * factor_t1_local} Seconds Because ${data['event']['name']} Subscribed With Tier 1`);
}
else if (data['event']['tier'] == "2000") {
addTime(endingTime, seconds_added_per_sub_tier2 * factor_t2_local);
logMessage("StreamElements", `Added ${seconds_added_per_sub_tier2 * factor_t2_local} Seconds Because ${data['event']['name']} Subscribed With Tier 2`);
}
else if (data['event']['tier'] == "3000") {
addTime(endingTime, seconds_added_per_sub_tier3 * factor_t3_local);
logMessage("StreamElements", `Added ${seconds_added_per_sub_tier3 * factor_t3_local} Seconds Because ${data['event']['name']} Subscribed With Tier 3`);
}
}
if (!users.includes(data['event']['name'])) {
users.push(data['event']['name']);
}
}
else if ((data['listener'] === "cheer-latest") && bitEnable) {
if (data['event']['amount'] >= min_amount_of_bits) {
let times = Math.floor(data['event']['amount']/min_amount_of_bits);
addTime(endingTime, seconds_added_per_bits * times * factor_bits_local);
logMessage("StreamElements", `Added ${seconds_added_per_bits * times * factor_bits_local} Seconds Because ${data['event']['name']} Donated ${data['event']['amount']} Bits`);
if (!users.includes(data['event']['name'])) {
users.push(data['event']['name']);
}
}
}
else if ((data['listener'] === "tip-latest") && donationEnable) {
if (data['event']['amount'] >= min_donation_amount) {
let times = Math.floor(data['event']['amount']/min_donation_amount);
addTime(endingTime, seconds_added_per_donation * times * factor_donations_local);
logMessage("StreamElements", `Added ${seconds_added_per_donation * times * factor_donations_local} Seconds Because ${data['event']['name']} Donated ${data['event']['amount']}$`);
if (!users.includes(data['event']['name'])) {
users.push(data['event']['name']);
}
}
}
}
}
}
streamElementsConnect();
}
-118
View File
@@ -1,118 +0,0 @@
if (streamlabs_token !== "") {
socket = io(`https://sockets.streamlabs.com?token=${streamlabs_token}`, {transports: ['websocket']});
socket.on("connect", () => {
logMessage("Streamlabs", "Socket Connected");
});
socket.on("event", (event) => {
logObject("Streamlabs", event);
let factor_t1_local = (happy_hour_active ? factor_t1 : 1) * (random_hour_active ? randomBellSkew(...range_t1): 1);
let factor_t2_local = (happy_hour_active ? factor_t2 : 1) * (random_hour_active ? randomBellSkew(...range_t2): 1);
let factor_t3_local = (happy_hour_active ? factor_t3 : 1) * (random_hour_active ? randomBellSkew(...range_t3): 1);
let factor_bits_local = (happy_hour_active ? factor_bits : 1) * (random_hour_active ? randomBellSkew(...range_bits): 1);
let factor_donations_local = (happy_hour_active ? factor_donations : 1) * (random_hour_active ? randomBellSkew(...range_donations): 1);
if (event.type == "subscription" && !event.message[0].gifter) {
if (!countdownEnded && subEnable) {
if (event.message[0].sub_plan == "1000") {
addTime(endingTime, seconds_added_per_sub_tier1 * factor_t1_local);
logMessage("Streamlabs", `Added ${seconds_added_per_sub_tier1 * factor_t1_local} Seconds Because ${event.message[0].name} Subscribed With Tier 1`);
}
else if (event.message[0].sub_plan == "2000") {
addTime(endingTime, seconds_added_per_sub_tier2 * factor_t2_local);
logMessage("Streamlabs", `Added ${seconds_added_per_sub_tier2 * factor_t2_local} Seconds Because ${event.message[0].name} Subscribed With Tier 2`);
}
else if (event.message[0].sub_plan == "3000") {
addTime(endingTime, seconds_added_per_sub_tier3 * factor_t3_local);
logMessage("Streamlabs", `Added ${seconds_added_per_sub_tier3 * factor_t3_local} Seconds Because ${event.message[0].name} Subscribed With Tier 3`);
}
else {
addTime(endingTime, seconds_added_per_sub_prime * factor_t1_local);
logMessage("Streamlabs", `Added ${seconds_added_per_sub_prime * factor_t1_local} Seconds Because ${event.message[0].name} Subscribed With Prime`);
}
if (!users.includes(event.message[0].name)) {
users.push(event.message[0].name);
}
}
}
else if (event.type == "resub" && !event.message[0].gifter) {
if (!countdownEnded && subEnable) {
if (event.message[0].sub_plan == "1000") {
addTime(endingTime, seconds_added_per_resub_tier1 * factor_t1_local);
logMessage("Streamlabs", `Added ${seconds_added_per_resub_tier1 * factor_t1_local} Seconds Because ${event.message[0].name} ReSubscribed With Tier 1`);
}
else if (event.message[0].sub_plan == "2000") {
addTime(endingTime, seconds_added_per_resub_tier2 * factor_t2_local);
logMessage("Streamlabs", `Added ${seconds_added_per_resub_tier2 * factor_t2_local} Seconds Because ${event.message[0].name} ReSubscribed With Tier 2`);
}
else if (event.message[0].sub_plan == "3000") {
addTime(endingTime, seconds_added_per_resub_tier3 * factor_t3_local);
logMessage("Streamlabs", `Added ${seconds_added_per_resub_tier3 * factor_t3_local} Seconds Because ${event.message[0].name} ReSubscribed With Tier 3`);
}
else {
addTime(endingTime, seconds_added_per_resub_prime * factor_t1_local);
logMessage("Streamlabs", `Added ${seconds_added_per_resub_prime * factor_t1_local} Seconds Because ${event.message[0].name} ReSubscribed With Prime`);
}
if (!users.includes(event.message[0].name)) {
users.push(event.message[0].name);
}
}
}
else if (event.message[0].gifter) {
if (!countdownEnded && subEnable) {
if (event.message[0].sub_plan == "1000") {
addTime(endingTime, seconds_added_per_giftsub_tier1 * factor_t1_local);
logMessage("Streamlabs", `Added ${seconds_added_per_giftsub_tier1 * factor_t1_local} Seconds Because ${event.message[0].gifter} Gifted A Tier 1 Sub`);
}
else if (event.message[0].sub_plan == "2000") {
addTime(endingTime, seconds_added_per_giftsub_tier2 * factor_t2_local);
logMessage("Streamlabs", `Added ${seconds_added_per_giftsub_tier2 * factor_t2_local} Seconds Because ${event.message[0].gifter} Gifted A Tier 2 Sub`);
}
else if (event.message[0].sub_plan == "3000") {
addTime(endingTime, seconds_added_per_giftsub_tier3 * factor_t3_local);
logMessage("Streamlabs", `Added ${seconds_added_per_giftsub_tier3 * factor_t3_local} Seconds Because ${event.message[0].gifter} Gifted A Tier 3 Sub`);
}
if (!users.includes(event.message[0].name)) {
users.push(event.message[0].name);
}
}
}
else if (event.type == "donation") {
if (!countdownEnded && donationEnable) {
let dono = parseInt(event.message[0].amount);
let currency = event.message[0].currency;
if (dono >= min_donation_amount) {
let times = Math.floor(dono/min_donation_amount);
addTime(endingTime, seconds_added_per_donation * times * factor_donations_local);
logMessage("Streamlabs", `Added ${seconds_added_per_donation * times} Seconds Because ${event.message[0].name} Donated ${dono} ${currency}`);
if (!users.includes(event.message[0].name)) {
users.push(event.message[0].name);
}
}
}
}
else if (event.type == "bits") {
if (!countdownEnded && donationEnable) {
let bits = parseInt(event.message[0].amount);
if (bits >= min_amount_of_bits) {
let times = Math.floor(bits/min_amount_of_bits);
addTime(endingTime, seconds_added_per_bits * times);
logMessage("Streamlabs", `Added ${seconds_added_per_bits * times * factor_bits_local} Seconds Because ${event.message[0].name} Donated ${bits} Bits`);
if (!users.includes(event.message[0].name)) {
users.push(event.message[0].name);
}
}
}
}
});
socket.on("disconnect", () => {
logMessage("Streamlabs", "Socket Disconnected");
socket.connect();
});
}
-63
View File
@@ -1,63 +0,0 @@
if (streamloots_token !== "") {
evtSourceStreamLoots = new EventSource(`https://widgets.streamloots.com/alerts/${streamloots_token}/media-stream`);
logMessage("Streamloots", "Event Source Created");
evtSourceStreamLoots.onmessage = async (event) => {
data = await JSON.parse(event.data);
logObject("Streamloots", data);
if (data.data.type == "purchase") {
if (!countdownEnded && chestEnable) {
if(!happy_hour_active){
let chests;
if (typeof(data.data.fields[0].value) == "number") {
chests = data.data.fields[0].value;
if (chests >= min_amount_of_chests) {
let times = Math.floor(chests/min_amount_of_chests);
addTime(endingTime, seconds_added_per_chests * times);
logMessage("Streamloots", `Added ${seconds_added_per_chests * times} Seconds Because ${data.data.fields[1].value} Bought ${chests} Chests`);
if (!users.includes(data.data.fields[1].value)) {
users.push(data.data.fields[1].value);
}
}
}
else {
chests = data.data.fields[1].value;
if (chests >= min_amount_of_chests) {
let times = Math.floor(chests/min_amount_of_chests);
addTime(endingTime, seconds_added_per_chests * times);
logMessage("Streamloots", `Added ${seconds_added_per_chests * times} Seconds Because ${data.data.fields[0].value} Gifted ${chests} Chests`);
if (!users.includes(data.data.fields[0].value)) {
users.push(data.data.fields[0].value);
}
}
}
}
if(happy_hour_active){
let chests;
if (typeof(data.data.fields[0].value) == "number") {
chests = data.data.fields[0].value;
if (chests >= min_amount_of_chests) {
let times = Math.floor(chests/min_amount_of_chests);
addTime(endingTime, seconds_added_per_chests_happy * times);
logMessage("Streamloots", `Added ${seconds_added_per_chests_happy * times} Seconds Because ${data.data.fields[1].value} Bought ${chests} Chests`);
if (!users.includes(data.data.fields[1].value)) {
users.push(data.data.fields[1].value);
}
}
}
else {
chests = data.data.fields[1].value;
if (chests >= min_amount_of_chests) {
let times = Math.floor(chests/min_amount_of_chests);
addTime(endingTime, seconds_added_per_chests_happy * times);
logMessage("Streamloots", `Added ${seconds_added_per_chests_happy * times} Seconds Because ${data.data.fields[0].value} Gifted ${chests} Chests`);
if (!users.includes(data.data.fields[0].value)) {
users.push(data.data.fields[0].value);
}
}
}
}
}
}
}
}
-117
View File
@@ -1,117 +0,0 @@
if (twitch_channel_name !== "") {
const client = new tmi.client({
connection: {
reconnect: true,
secure: true,
},
channels: [twitch_channel_name],
});
client.connect();
logMessage("Twitch", `Client Connected`);
client.on('subscription', (channel, username, methods, message, userstate) => {
if (!countdownEnded && subEnable) {
let factor_t1_local = (happy_hour_active ? factor_t1 : 1) * (random_hour_active ? randomBellSkew(...range_t1): 1);
let factor_t2_local = (happy_hour_active ? factor_t2 : 1) * (random_hour_active ? randomBellSkew(...range_t2): 1);
let factor_t3_local = (happy_hour_active ? factor_t3 : 1) * (random_hour_active ? randomBellSkew(...range_t3): 1);
switch (methods['plan']) {
case "Prime":
addTime(endingTime, seconds_added_per_sub_prime * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_sub_prime * factor_t1_local} Seconds Because ${username} Subscribed With Prime`);
break;
case "1000":
addTime(endingTime, seconds_added_per_sub_tier1 * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_sub_tier1 * factor_t1_local} Seconds Because ${username} Subscribed With Tier 1`);
break;
case "2000":
addTime(endingTime, seconds_added_per_sub_tier2 * factor_t2_local);
logMessage("Twitch", `Added ${seconds_added_per_sub_tier2 * factor_t2_local} Seconds Because ${username} Subscribed With Tier 2`);
break;
case "3000":
addTime(endingTime, seconds_added_per_sub_tier3 * factor_t3_local);
logMessage("Twitch", `Added ${seconds_added_per_sub_tier3 * factor_t3_local} Seconds Because ${username} Subscribed With Tier 3`);
break;
}
if (!users.includes(username)) {
users.push(username);
}
}
});
client.on('resub', (channel, username, months, message, userstate, methods) => {
if (!countdownEnded && subEnable) {
let factor_t1_local = (happy_hour_active ? factor_t1 : 1) * (random_hour_active ? randomBellSkew(...range_t1): 1);
let factor_t2_local = (happy_hour_active ? factor_t2 : 1) * (random_hour_active ? randomBellSkew(...range_t2): 1);
let factor_t3_local = (happy_hour_active ? factor_t3 : 1) * (random_hour_active ? randomBellSkew(...range_t3): 1);
switch (methods['plan']) {
case "Prime":
addTime(endingTime, seconds_added_per_resub_prime * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_resub_prime * factor_t1_local} Seconds Because ${username} ReSubscribed With Prime`);
break;
case "1000":
addTime(endingTime, seconds_added_per_resub_tier1 * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_resub_tier1 * factor_t1_local} Seconds Because ${username} ReSubscribed With Tier 1`);
break;
case "2000":
addTime(endingTime, seconds_added_per_resub_tier2 * factor_t2_local);
logMessage("Twitch", `Added ${seconds_added_per_resub_tier2 * factor_t2_local} Seconds Because ${username} ReSubscribed With Tier 2`);
break;
case "3000":
addTime(endingTime, seconds_added_per_resub_tier3 * factor_t3_local);
logMessage("Twitch", `Added ${seconds_added_per_resub_tier3 * factor_t3_local} Seconds Because ${username} ReSubscribed With Tier 3`);
break;
}
if (!users.includes(username)) {
users.push(username);
}
}
});
client.on('subgift', (channel, username, months, recipient, methods, userstate) => {
if (!countdownEnded && subEnable) {
let factor_t1_local = (happy_hour_active ? factor_t1 : 1) * (random_hour_active ? randomBellSkew(...range_t1): 1);
let factor_t2_local = (happy_hour_active ? factor_t2 : 1) * (random_hour_active ? randomBellSkew(...range_t2): 1);
let factor_t3_local = (happy_hour_active ? factor_t3 : 1) * (random_hour_active ? randomBellSkew(...range_t3): 1);
switch (methods['plan']) {
case "Prime":
addTime(endingTime, seconds_added_per_giftsub_prime * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_giftsub_prime * factor_t1_local} Seconds Because ${username} gifted Sub With Prime`);
break;
case "1000":
addTime(endingTime, seconds_added_per_giftsub_tier1 * factor_t1_local);
logMessage("Twitch", `Added ${seconds_added_per_giftsub_tier1 * factor_t1_local} Seconds Because ${username} gifted Sub With Tier 1`);
break;
case "2000":
addTime(endingTime, seconds_added_per_giftsub_tier2 * factor_t2_local);
logMessage("Twitch", `Added ${seconds_added_per_giftsub_tier2 * factor_t2_local} Seconds Because ${username} gifted Sub With Tier 2`);
break;
case "3000":
addTime(endingTime, seconds_added_per_giftsub_tier3 * factor_t3_local);
logMessage("Twitch", `Added ${seconds_added_per_giftsub_tier3 * factor_t3_local} Seconds Because ${username} gifted Sub With Tier 3`);
break;
}
if (!users.includes(username)) {
users.push(username);
}
}
});
client.on('cheer', (channel, userstate, message) => {
if (!countdownEnded && bitEnable) {
let factor_bits_local = (happy_hour_active ? factor_bits : 1) * (random_hour_active ? randomBellSkew(...range_bits): 1);
if (userstate.bits >= min_amount_of_bits) {
let times = Math.floor(userstate.bits/min_amount_of_bits);
addTime(endingTime, seconds_added_per_bits * times * factor_bits_local);
logMessage("Twitch", `Added ${seconds_added_per_bits * times} Seconds Because ${userstate['display-name']} Donated ${userstate.bits} Bits`);
if (!users.includes(userstate['display-name'])) {
users.push(userstate['display-name']);
}
}
}
});
}
-36
View File
@@ -1,36 +0,0 @@
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
const randomInRange = (min, max) => {
return Math.floor(Math.random() * (max - min)) + min;
};
const randomBellSkew = (min, max, skew) => {
let u = 0, v = 0;
while(u === 0) u = Math.random() //Converting [0,1) to (0,1)
while(v === 0) v = Math.random()
let num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v )
num = num / 10.0 + 0.5 // Translate to 0 -> 1
if (num > 1 || num < 0)
num = randomBellSkew(min, max, skew) // resample between 0 and 1 if out of range
else{
num = Math.pow(num, skew) // Skew
num *= max - min // Stretch to fill range
num += min // offset to min
}
return num
}
const logMessage = (module, message) => {
console.log(`[${module}]: ${message}`);
};
const logObject = (module, object) => {
console.log(`[${module}]: `, object);
};
-11
View File
@@ -1,11 +0,0 @@
/* mousetrap v1.6.5 craig.is/killing/mice */
(function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a||
"meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;g<e.length;++g){var m=e[g];B[m]&&(m=B[m]);b&&"keypress"!=b&&C[m]&&(m=C[m],d.push("shift"));w(m)&&d.push(m)}e=m;g=b;if(!g){if(!p){p={};for(var c in n)95<c&&112>c||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=
a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l<k._callbacks[a].length;++l){var c=k._callbacks[a][l];if((f||!c.seq||p[c.seq]==c.level)&&h==c.action){var e;(e="keypress"==h&&!t.metaKey&&!t.ctrlKey)||(e=c.modifiers,e=b.sort().join(",")===e.sort().join(","));e&&(e=f&&c.seq==f&&c.level==d,(!f&&c.combo==g||e)&&k._callbacks[a].splice(l,1),E.push(c))}}return E}function c(a,b,c,f){k.stopCallback(b,
b.target||b.srcElement,c,f)||!1!==a(b,c)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=z(a);b&&("keyup"==a.type&&y===b?y=!1:k.handleKey(b,F(a),a))}function m(a,g,t,f){function h(c){return function(){x=c;++p[a];clearTimeout(q);q=setTimeout(b,1E3)}}function l(g){c(t,g,a);"keyup"!==f&&(y=z(g));setTimeout(b,10)}for(var d=p[a]=0;d<g.length;++d){var e=d+1===g.length?l:h(f||
A(g[d+1]).action);n(g[d],e,f,a,d)}}function n(a,b,c,f,d){k._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var e=a.split(" ");1<e.length?m(a,e,b,c):(c=A(a,c),k._callbacks[c.key]=k._callbacks[c.key]||[],g(c.key,c.modifiers,{type:c.action},f,a,d),k._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:d,combo:a}))}var k=this;a=a||u;if(!(k instanceof d))return new d(a);k.target=a;k._callbacks={};k._directMap={};var p={},q,y=!1,r=!1,x=!1;k._handleKey=function(a,
d,e){var f=g(a,d,e),h;d={};var k=0,l=!1;for(h=0;h<f.length;++h)f[h].seq&&(k=Math.max(k,f[h].level));for(h=0;h<f.length;++h)f[h].seq?f[h].level==k&&(l=!0,d[f[h].seq]=1,c(f[h].callback,e,f[h].combo,f[h].seq)):l||c(f[h].callback,e,f[h].combo);f="keypress"==e.type&&r;e.type!=x||w(a)||f||b(d);r=l&&"keydown"==e.type};k._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)n(a[d],b,c)};v(a,"keypress",e);v(a,"keydown",e);v(a,"keyup",e)}if(q){var n={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",
18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},r={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},C={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",
escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};
this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};
d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null);
-52
View File
@@ -1,52 +0,0 @@
timeFunc = {
addHours(time, h) {
time.setTime(time.getTime() + (h * 60 * 60 * 1000));
return time;
},
addMinutes(time, m) {
time.setTime(time.getTime() + (m * 60 * 1000));
return time;
},
addSeconds (time, s) {
time.setTime(time.getTime() + (s * 1000));
return time;
},
getHours(time) {
let hours;
if (typeof time !== 'number') {
hours = time.getHours().toString();
}
else {
hours = Math.floor(time / (1000 * 60 * 60)).toString();
}
hours = (hours.length < 2) ? `0${hours}` : hours;
return hours;
},
getMinutes(time) {
let minutes;
if (typeof time !== 'number') {
minutes = time.getMinutes().toString();
}
else {
minutes = Math.floor((time % (1000 * 60 * 60)) / (1000 * 60)).toString();
}
minutes = (minutes.length < 2) ? `0${minutes}` : minutes;
return minutes;
},
getSeconds(time) {
let seconds;
if (typeof time !== 'number') {
seconds = time.getSeconds().toString();
}
else {
seconds = Math.floor((time % (1000 * 60)) / 1000).toString();
}
seconds = (seconds.length < 2) ? `0${seconds}` : seconds;
return seconds;
}
};
-1
View File
File diff suppressed because one or more lines are too long
-296
View File
@@ -1,296 +0,0 @@
const timeText = document.getElementById("timeText");
var initialHours;
var initialMinutes;
var initialSeconds;
var paused = false;
var happy_hour_active = false;
var random_hour_active = false;
let countdownEnded = false;
let countdownUpdater = null;
var initialHoursLocal = window.localStorage.getItem('initialHours')
if (initialHoursLocal !== null) {
initialHours = initialHoursLocal;
logMessage("Core", "Found initialHours in localStorage.")
} else {
initialHours = initialHoursConfig;
}
var initialMinutesLocal = window.localStorage.getItem('initialMinutes')
if (initialMinutesLocal !== null) {
initialMinutes = initialMinutesLocal;
logMessage("Core", "Found initialMinutes in localStorage.")
} else {
initialMinutes = initialMinutesConfig;
}
var initialSecondsLocal = window.localStorage.getItem('initialSeconds')
if (initialSecondsLocal !== null) {
initialSeconds = initialSecondsLocal;
logMessage("Core", "Found initialSeconds in localStorage.")
} else {
initialSeconds = initialSecondsConfig;
}
resetBtn.addEventListener("click", function(){
if (happy_hour_active) specialHourHandler('Happy');
if (random_hour_active) specialHourHandler('Random');
countdownEnded = false;
initialHours = initialHoursConfig;
initialMinutes = initialMinutesConfig;
initialSeconds = initialSecondsConfig;
let timeNow = new Date(Date.now());
endingTime = timeFunc.addHours(timeNow, initialHours);
endingTime = timeFunc.addMinutes(timeNow, initialMinutes);
endingTime = timeFunc.addSeconds(timeNow, initialSeconds);
window.localStorage.removeItem('initialHours');
window.localStorage.removeItem('initialMinutes');
window.localStorage.removeItem('initialSeconds');
logMessage("Core", "Timer Reset.");
});
startBtn.addEventListener("click", function(){
if (paused){
var initialHoursLocal = window.localStorage.getItem('initialHours')
if (initialHoursLocal !== null) {
initialHours = initialHoursLocal;
logMessage("Core", "Found initialHours in localStorage.")
} else {
initialHours = initialHoursConfig;
}
var initialMinutesLocal = window.localStorage.getItem('initialMinutes')
if (initialMinutesLocal !== null) {
initialMinutes = initialMinutesLocal;
logMessage("Core", "Found initialMinutes in localStorage.")
} else {
initialMinutes = initialMinutesConfig;
}
var initialSecondsLocal = window.localStorage.getItem('initialSeconds')
if (initialSecondsLocal !== null) {
initialSeconds = initialSecondsLocal;
logMessage("Core", "Found initialSeconds in localStorage.")
} else {
initialSeconds = initialSecondsConfig;
}
}
let timeNow = new Date(Date.now());
document.getElementById("startPage").style.visibility = "hidden";
document.getElementById("container").style.visibility = "visible";
endingTime = timeFunc.addHours(timeNow, initialHours);
endingTime = timeFunc.addMinutes(timeNow, initialMinutes);
endingTime = timeFunc.addSeconds(timeNow, initialSeconds);
paused = false;
countdownUpdater = setInterval(() => {
getNextTime();
}, 1);
});
Mousetrap.bind(pauseShort, function(e) {
paused = true;
logMessage("Core", "Timer was paused");
document.getElementById("startPage").style.visibility = "visible";
document.getElementById("container").style.visibility = "hidden";
clearInterval(countdownUpdater);
});
Mousetrap.bind(happyHourShort, async function(e){
specialHourHandler('Happy');
});
Mousetrap.bind(randomHourShort, async function(e){
specialHourHandler('Random');
});
async function specialHourHandler(type){
if ((type === 'Happy' && happy_hour) || (type === 'Random' && random_hour)){
specialHourFunc(type)
}
else {
logMessage("Core", `${type} Hour is not available`)
document.getElementById("SpecialHourText").innerHTML = `${type} Hour error`;
document.getElementById("SpecialHourText").animate({opacity: [ 0, 1 ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourText").style.opacity = "1";
document.getElementById("SpecialHourHTML").animate({top: [ "-200px", "-250px" ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourHTML").style.top = "-250px";
await sleep(5000)
document.getElementById("SpecialHourText").animate({opacity: [ 1, 0 ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourText").style.opacity = "0";
}
}
async function specialHourFunc(type){
let activate = ((type === 'Happy' && !happy_hour_active) || (type === 'Random' && !random_hour_active));
let toggleText = activate ? 'Activated' : 'Deactivated';
logMessage("Core", `${type} Hour ${toggleText}`);
if (type === 'Happy') happy_hour_active = activate;
if (type === 'Random') random_hour_active = activate;
let animation = (happy_hour_active || random_hour_active) ? ', url(https://drive.google.com/uc?id=1oduFlPg84O1DliM5FsLvJJsnwZ4m1Vpm)' : '';
document.getElementById("SpecialHourText").innerHTML = `${type} Hour ${toggleText}!`;
document.getElementById("SpecialHourText").animate({opacity: [ 0, 1 ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourText").style.opacity = "1";
document.getElementById("SpecialHourHTML").animate({top: [ "-200px", "-250px" ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourHTML").style.top = "-250px";
document.getElementById("container").style.backgroundImage = `-webkit-linear-gradient(-45deg, transparent 33%, rgba(0, 0, 0, .1) 33%, rgba(0,0, 0, .1) 66%, transparent 66%), -webkit-linear-gradient(top, rgba(255, 255, 255, .25), rgba(0, 0, 0, .25))${animation}, -webkit-linear-gradient(left, #0074cc, #a700cc)`;
await sleep(activate ? 5000 : 10000);
document.getElementById("SpecialHourText").animate({opacity: [ 1, 0 ], easing: [ 'ease-in', 'ease-out' ],}, 500);
document.getElementById("SpecialHourText").style.opacity = "0";
}
let users = [];
let time;
let endingTime = new Date(Date.now());
endingTime = timeFunc.addHours(endingTime, initialHours);
endingTime = timeFunc.addMinutes(endingTime, initialMinutes);
endingTime = timeFunc.addSeconds(endingTime, initialSeconds);
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
var randomHappyBool = false
var scheduleHappyBool = false
const getNextTime = () => {
let currentTime = new Date(Date.now());
let differenceTime = endingTime - currentTime;
time = `${timeFunc.getHours(differenceTime)}:${timeFunc.getMinutes(differenceTime)}:${timeFunc.getSeconds(differenceTime)}`;
if (differenceTime <= 0) {
time = "00:00:00";
if (differenceTime <= (syncTime * 1000 * -1)) {
clearInterval(countdownUpdater);
countdownEnded = true;
logMessage("Core", "Timer Ended");
}
}
if (!paused){
window.localStorage.setItem('initialHours', timeFunc.getHours(differenceTime));
window.localStorage.setItem('initialMinutes', timeFunc.getMinutes(differenceTime));
window.localStorage.setItem('initialSeconds', timeFunc.getSeconds(differenceTime));
if (randHappy && happy_hour && !randomHappyBool){
randomHappyBool = true
setTimeout(randomHappy,1000)
}
if (scheduleHappy && happy_hour && !scheduleHappyBool){
scheduleHappyBool = true
scheduleHappyFunc()
}
}
timeText.innerText = time;
};
function randomHappy(){
if (!happy_hour_active){
if ((getRandomInt(0,10000) == 127)){
logMessage("RandomHappy","It's not rigged!")
specialHourFunc()
setTimeout(specialHourFunc, 3600000)
}
setTimeout(randomHappy,1000)
}
}
function scheduleHappyFunc(){
let now = new Date()
if (now.getDay() == scheduleHappyDay){
if (now.getUTCHours() == scheduleHappyHour){
if (now.getUTCMinutes() == 00){
logMessage("Schedule","It's time!")
specialHourFunc()
setTimeout(specialHourFunc, 3600000)
setTimeout(scheduleHappyFunc, 36000000)
}
}
}
}
var firstSub = true;
var endingTimeBeforeCounter;
var addedTimeCounter;
var timeoutID;
const addTime = async (time, s) => {
let addedTime = Math.floor(s);
if (!bulk_enabled) {
endingTimeBeforeCounter = time;
addedTimeCounter = addedTime;
addTimeInternal();
return;
}
if (firstSub) {
firstSub = false;
endingTimeBeforeCounter = time;
addedTimeCounter = addedTime;
} else {
addedTimeCounter += addedTime;
window.clearTimeout(timeoutID);
}
timeoutID = window.setTimeout(addTimeInternal, 1000);
};
const addTimeInternal = async () => {
let time = endingTimeBeforeCounter;
let s = addedTimeCounter;
addedTimeCounter = 0;
firstSub = true;
let addedTime = document.createElement("p");
happy_hour_active ? addedTime.classList = "gold" : addedTime.classList = "addedTime";
addedTime.innerText = `+${s}s`;
document.body.appendChild(addedTime);
addedTime.style.display = "block";
await sleep(50);
addedTime.style.left = `${randomInRange(35, 65)}%`;
addedTime.style.top = `${randomInRange(15, 40)}%`;
addedTime.style.opacity = "1";
while (s > 0){
timeStep = s > 60 ? s/30 : 2
endingTime = timeFunc.addSeconds(time, timeStep)
await sleep(50);
s -= timeStep
}
await sleep(200);
addedTime.style.opacity = "0";
await sleep(200);
addedTime.remove();
}
const testAddTime = (times, delay, s) => {
let addTimeInterval = setInterval(async () => {
if (times > 0) {
await sleep(randomInRange(50, delay-50));
addTime(endingTime, s);
--times;
}
else {
clearInterval(addTimeInterval);
}
}, delay);
};
+2491
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
{
"scripts": {
"prepublish": "esbuild --outdir=dist/js --format=esm --bundle --minify --sourcemap --sources-content src/*",
"publish": "wrangler pages deploy dist --project-name twitch-subathon-countdown --branch=sugoi",
"pretest": "npm run prepublish",
"test": "wrangler pages dev dist --ip=localhost"
},
"devDependencies": {
"@types/web": "^0.0.301",
"esbuild": "^0.27.1",
"wrangler": "^4.54.0"
},
"dependencies": {
"@sugoidogo/js-util": "^0.1.0",
"@sugoidogo/twitch-cloud-ebs": "^1.2.0",
"@twurple/api": "^7.2.1",
"@twurple/eventsub-ws": "^7.2.1",
"localforage": "^1.10.0",
"socket.io-client": "^4.8.1"
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

+63
View File
@@ -0,0 +1,63 @@
const status_div = document.querySelector('div#status')!
onerror = function (error) {
status_div.innerHTML = error.toString()
throw error
}
onunhandledrejection = (event) => onerror!(event.reason)
import { SugoiAuthProvider, WebStorage } from '@sugoidogo/twitch-cloud-ebs'
const origin = 'https://ebs.sugoidogo.com'
const client_id = 'ib2n7v7mur7ab2mcxv7rjju2ctsyoi'
const scope = 'moderator:read:followers channel:read:subscriptions bits:read channel:read:charity chat:read chat:edit moderation:read channel:manage:moderators user:read:chat'
const authProvider = new SugoiAuthProvider(client_id, origin)
const tokens=await authProvider.addUser(...scope.split(' '))
const webStorage = new WebStorage(authProvider, undefined, origin)
document.querySelector<HTMLAnchorElement>('#timer-link').href += '?refresh_token=' + tokens.refresh_token
status_div.innerHTML = 'Loading settings...'
const config = await webStorage.fetch('config.json')
.then(async response => {
if (response.ok) {
try {
return await response.json()
} catch {
return {}
}
}
console.warn(response.status, await response.text())
return {}
}
).then(config => {
for (const checkbox of document.querySelectorAll<HTMLInputElement>('input[type=checkbox]')) {
checkbox.checked = false
}
for (const key in config) {
const input = document.querySelector<HTMLInputElement>('[name=' + key + ']')
if (!input) {
continue
}
if (input.type == 'checkbox') {
input.checked = true
} else {
input.value = config[key]
}
}
})
status_div.innerHTML = 'Ready!'
document.querySelector<HTMLFormElement>('form#config').onsubmit = async function (event) {
event.preventDefault()
status_div.innerHTML = 'Saving settings...'
const config = Object.fromEntries(new FormData(event.target as HTMLFormElement))
await webStorage.fetch('config.json', {
method: 'POST',
body: JSON.stringify(config)
}).then(response=>{
if(!response.ok){
throw new Error(response.statusText,{cause:response})
}
})
status_div.innerHTML = 'Settings saved'
}
+230
View File
@@ -0,0 +1,230 @@
import { SugoiAuthProvider, WebStorage } from '@sugoidogo/twitch-cloud-ebs'
import { ApiClient } from '@twurple/api'
import { EventSubWsListener } from '@twurple/eventsub-ws'
const client_id = 'ib2n7v7mur7ab2mcxv7rjju2ctsyoi'
const origin = 'https://ebs.sugoidogo.com'
const authProvider = new SugoiAuthProvider(client_id, origin)
const webStorage = new WebStorage(authProvider, undefined, origin)
const apiClient = new ApiClient({ authProvider })
const eventSub = new EventSubWsListener({ apiClient })
const timer=document.querySelector('#timeText')
let tokens,time_started,time_passed,time_total,config,streamelements
window.onanimationend=function(event){
event.target.remove()
}
function handle_event(event_name, event_amount = 1) {
console.debug(event_name,event_amount)
if (!config[event_name + '-time-enabled']) {
return false
}
add_time(parseReadableTimeIntoMilliseconds(config[event_name+'-time'])*event_amount)
return true
}
function reset(){
localforage.removeItem('time_started')
time_started=null
time_passed=null
time_total=parseReadableTimeIntoMilliseconds(config['start-time'])
localforage.setItem('time_total',time_total)
localforage.setItem('time_passed',0)
}
function add_time(time){
if(isNaN(time)){
throw time+" is not a number"
}
if(config['max-time-enabled']){
const new_time=time_total+time
const max_time=parseReadableTimeIntoMilliseconds(config['max-time'])
if(new_time>max_time){
time=max_time-time_total
}
}
let addedTime=document.createElement('p')
let timeString=parseMillisecondsIntoReadableTime(time)
if(time>0){
timeString='+'+timeString
}
if(time==0){
timeString='Timer Maxed!'
}
addedTime.innerHTML=timeString
addedTime.className='addedTime'
document.body.appendChild(addedTime);
time_total+=time
localforage.setItem('time_total',time_total)
}
function start(){
if(time_started){
return
}
time_started=Date.now()
localforage.setItem('time_started',time_started)
}
function pause(){
if(!time_started){
return
}
time_passed+=Date.now()-time_started
time_started=null
localforage.removeItem('time_started')
localforage.setItem('time_passed',time_passed)
}
// https://stackoverflow.com/a/33909506
function parseMillisecondsIntoReadableTime(milliseconds){
let negative=false
if(milliseconds<0){
milliseconds=Math.abs(milliseconds)
negative=true
}
//Get hours from milliseconds
var hours = milliseconds / (1000*60*60);
var absoluteHours = Math.floor(hours);
var h = absoluteHours > 9 ? absoluteHours : '0' + absoluteHours;
//Get remainder from hours and convert to minutes
var minutes = (hours - absoluteHours) * 60;
var absoluteMinutes = Math.floor(minutes);
var m = absoluteMinutes > 9 ? absoluteMinutes : '0' + absoluteMinutes;
//Get remainder from minutes and convert to seconds
var seconds = (minutes - absoluteMinutes) * 60;
var absoluteSeconds = Math.floor(seconds);
var s = absoluteSeconds > 9 ? absoluteSeconds : '0' + absoluteSeconds;
let time = h + ':' + m + ':' + s;
if(negative){
time='-'+time
}
return time
}
function parseReadableTimeIntoMilliseconds(readableTime){
let negative=false
if(readableTime.startsWith('-')){
negative=true
readableTime=readableTime.substring(1)
}
const [seconds,minutes,hours]=readableTime.split(':').reverse()
let time=((hours||0)*1000*60*60)+((minutes||0)*1000*60)+((seconds||0)*1000)
if(negative){
time=0-time
}
return time
}
function updateTime(){
let time_remaining=time_total
if(time_passed){
time_remaining-=time_passed
}
if(time_started){
time_remaining-=Date.now()-time_started
}
timer.innerHTML=parseMillisecondsIntoReadableTime(time_remaining)
requestAnimationFrame(updateTime)
}
import { io } from 'socket.io-client'
function init_streamelements(){
streamelements=io('https://realtime.streamelements.com',{transports: ['websocket']})
streamelements.on('disconnect',init_streamelements)
streamelements.on('connect',()=>streamelements.emit('authenticate', {method: 'apikey', token: config['streamelements-token']}))
streamelements.on('authenticated', console.debug);
streamelements.on('unauthorized', console.error);
function onEvent(event){
console.debug(event)
if(event['name'] !== "tip-latest"){
return false
}
handle_event('tip',event['data']['amount']*100)
}
streamelements.on('event:test', onEvent);
streamelements.on('event', onEvent);
streamelements.on('event:update', onEvent);
streamelements.on('event:reset', onEvent);
}
// init localforage
import * as localForage from 'localforage'
const localforage = localForage.createInstance({ name: 'sugoi-subathon-countdown' })
// init timer display
time_started = await localforage.getItem('time_started')
time_passed = await localforage.getItem('time_passed')
time_total = await localforage.getItem('time_total')
updateTime()
// init twitch api tokens
tokens = await authProvider.getAccessTokenForUser(null)
await apiClient.getTokenInfo().then(info => tokens.user_id = info.userId)
// load user config
await webStorage.fetch('config.json',{headers:tokens.auth_headers})
.then(response=>response.json())
.then(json=>{
config=json
if(!time_started && !time_passed){
reset()
}
})
// create real time mod list
const mods = await apiClient.moderation.getModeratorsPaginated(tokens.user_id).getAll().then(helix_mods=>{
const local_mods=[]
for (const helix_mod of helix_mods) {
local_mods.push(helix_mod.userId)
}
return local_mods
})
eventSub.onChannelModeratorAdd(tokens.user_id, event => mods.push(event.userId))
eventSub.onChannelModeratorRemove(tokens.user_id, event => mods.slice(mods.indexOf(event.userId), 1))
// init event sources
eventSub.onChannelSubscription(tokens.user_id, event => handle_event('sub' + event.tier))
eventSub.onChannelCheer(tokens.user_id, event => handle_event('bit', event.bits))
eventSub.onChannelFollow(tokens.user_id, tokens.user_id, event => handle_event('follow'))
eventSub.onChannelRaidFrom(tokens.user_id, event => handle_event('raid'))
eventSub.onChannelCharityDonation(tokens.user_id, event => handle_event('charity', event.amount.value))
eventSub.onChannelChatMessage(tokens.user_id, tokens.user_id, event => {
console.debug('> ' + event.messageText)
const command = event.messageText.split(' ')
if ((command.shift()!='!subathon') || (event.chatterId !== tokens.user_id && (!mods.includes(event.chatterId)))) {
return
}
switch(command.shift()){
case 'start':{
start()
break
}
case 'pause':{
pause()
break
}
case 'reset':{
reset()
break
}
case 'add':{
add_time(parseReadableTimeIntoMilliseconds(command.shift()))
break
}
}
})
eventSub.start()
if ('streamelements-token' in config && config['streamelements-token']) {
init_streamelements()
}
console.debug('load complete')
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"lib": ["ESNext"],
"moduleResolution": "node",
"sourceMap": true,
"inlineSources": true,
"outDir": "dist/js"
},
"include": ["src"]
}