Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ca7c28e77 | ||
|
|
a3e69d4b68 | ||
|
|
809d3ed5a2 | ||
|
|
a8a7ec7109 | ||
|
|
12dc1b886f | ||
|
|
c835c6514e | ||
|
|
d56a11c482 | ||
|
|
d1abbca707 | ||
|
|
4fc82f50f5 | ||
|
|
be87605e43 | ||
|
|
f699248865 | ||
|
|
9c07d2345d | ||
|
|
b5779bfd70 | ||
|
|
46c92654bc | ||
|
|
0a5191063d | ||
|
|
7cd791b7df | ||
|
|
3f1fe62926 | ||
|
|
343ea2e2e9 | ||
|
|
f4a7b57968 | ||
|
|
7b10257261 | ||
|
|
7912055962 | ||
|
|
20bc1a89c8 | ||
|
|
81fc3daf42 | ||
|
|
2d5ec72582 | ||
|
|
b7108f729b | ||
|
|
625fd31551 | ||
|
|
3748956e5d | ||
|
|
2f8bcbb938 | ||
|
|
8f30c52f47 | ||
|
|
e2eafaf3f5 | ||
|
|
3dd7d7fedc | ||
|
|
3f98d9483e | ||
|
|
27cfcc800a | ||
|
|
0300183adc | ||
|
|
28cc128605 | ||
|
|
8aabc03604 | ||
|
|
91141fc5f6 | ||
|
|
ffb6a97bfe | ||
|
|
9de8cc30bc | ||
|
|
22070384b4 | ||
|
|
9a48bf8b2b | ||
|
|
fa4ce9608a | ||
|
|
fd41eead59 | ||
|
|
dd94e42851 | ||
|
|
907cbea17a | ||
|
|
7d71419125 | ||
|
|
d72a2bd2f5 | ||
|
|
aca8b21945 |
@@ -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 }}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
dist/js
|
||||||
|
.wrangler
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@sugoidogo:registry=https://gitea.sugoidogo.com/api/packages/sugoidogo/npm/
|
||||||
@@ -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>
|

|
||||||
<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 |
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
Vendored
+79
@@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+98
@@ -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>
|
||||||
Vendored
+21
@@ -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
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Vendored
-11
@@ -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);
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Vendored
-1
File diff suppressed because one or more lines are too long
-296
@@ -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);
|
|
||||||
};
|
|
||||||
Generated
+2491
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 MiB |
@@ -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
@@ -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')
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"module": "es2022",
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"outDir": "dist/js"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user