46 Commits
Author SHA1 Message Date
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 3057 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>
<p align="center">This is a Twitch Subathon Countdown originally by JayexDesigns.</p>
<p align="center">The CSS Code is updated by Johnnycyan. The JS Files are updated by leabdd and danilotitato to add functionalities.</p>
<br/>
# Improved Twitch Subathon Countdown
This is a Twitch Subathon Countdown originally by JayexDesigns.
<h2>Description</h2>
<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/>
The CSS Code is updated by Johnnycyan.
<h2>Shortcuts</h2>
<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/>
The JS Files have been re-written by SugoiDogo.
<h2>Preview</h2>
<h3 align="center"><img width="90%" src="./preview.webp"></h3>
<br/>
## Description
A subathon timer that increases when someone subscribes, donates to your charity, donates bits, follows or raids.
<h2>Usage</h2>
<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>
## Usage
Go to the [config page](https://subathon.sugoidogo.com/config.html), authorize the timer, and configure to your liking.
<h3>Twitch</h3>
<p>To get your Subs and Bits you just have to add your Channel Name.</p>
### StreamElements Tips
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>
<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">
![StreamElements Overlay Token Image](auth_streamelements.png)
<h3>StreamElements Token</h3>
<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>
<img src="./auth_streamelements.png">
<h3>Streamloots Token</h3>
<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>
<img src="./auth_streamloots.png">
<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>
## Missing Features
The original version of this overlay included support for events from StreamLabs and Streamloots,
and the version this is forked from included additional features by leabdd and danilotitato,
such as Random Happy Hour and Bulk Events.
This fork is an almost total re-write of the core JS code behind the timer,
so those features will also have to be re-written to be included in this fork.
Future versions of this timer will include a plug-in loader,
allowing additional features and event sources to be added without additional forking.
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%;
}
}
+98
View File
@@ -0,0 +1,98 @@
<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
+20
View File
@@ -0,0 +1,20 @@
{
"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",
"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 '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"]
}