various updates.

This commit is contained in:
Khyretos 2023-12-28 16:25:52 +01:00
parent 914cf831c4
commit 525cb6116e
45 changed files with 5022 additions and 5277 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

5
.eslintignore Normal file
View file

@ -0,0 +1,5 @@
node_modules
docs
dist
out
build

16
.eslintrc.js Normal file
View file

@ -0,0 +1,16 @@
module.exports = {
root: true,
env: {
es6: true,
node: true
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021
},
extends: ['eslint:recommended', '@electron-internal', '@electron-toolkit'],
rules: {
'space-before-function-paren': 'off',
vendorPrefix: 'off'
}
};

5
.prettierrc.yaml Normal file
View file

@ -0,0 +1,5 @@
singleQuote: true
semi: true
printWidth: 140
trailingComma: none
arrowParens: avoid

View file

@ -19,6 +19,7 @@
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"emoji-picker-element": "^1.21.0",
"express": "^4.18.2", "express": "^4.18.2",
"ini": "^2.0.0", "ini": "^2.0.0",
"kill-process-by-name": "^1.0.5", "kill-process-by-name": "^1.0.5",
@ -38,6 +39,12 @@
"@electron-forge/maker-squirrel": "^6.2.1", "@electron-forge/maker-squirrel": "^6.2.1",
"@electron-forge/maker-zip": "^6.2.1", "@electron-forge/maker-zip": "^6.2.1",
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1", "@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
"electron": "^25.9.8" "@electron-internal/eslint-config": "^1.0.1",
"@electron-toolkit/eslint-config": "^1.0.2",
"electron": "^25.9.8",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.2",
"prettier": "^3.1.1"
} }
} }

View file

@ -1,7 +1,15 @@
# from wsgiref.simple_server import WSGIServer
from flask import Flask, Response, jsonify, request from flask import Flask, Response, jsonify, request
import gevent import gevent
import re
import gevent.monkey import gevent.monkey
import json import json
from waitress import serve
import logging
logger = logging.getLogger("waitress")
logger.setLevel(logging.INFO)
gevent.monkey.patch_all() gevent.monkey.patch_all()
import gevent.queue import gevent.queue
@ -40,6 +48,7 @@ q = queue.Queue()
# gobal functions # gobal functions
# classes # classes
class LanguageDetection: class LanguageDetection:
def __init__(self): def __init__(self):
@ -56,9 +65,7 @@ class LanguageDetection:
resources_folder, "language_detection_model", f"lid.176.bin" resources_folder, "language_detection_model", f"lid.176.bin"
) )
language_detection_model = ( language_detection_model = rf"{language_detection_model}"
rf"{language_detection_model}"
)
self.model = fasttext.load_model(language_detection_model) self.model = fasttext.load_model(language_detection_model)
def predict_lang(self, text): def predict_lang(self, text):
@ -69,6 +76,7 @@ class LanguageDetection:
return language_codes return language_codes
class STT: class STT:
samplerate = None samplerate = None
args = "" args = ""
@ -92,9 +100,7 @@ class STT:
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"] resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
) )
self.model = Model( self.model = Model(rf"{vosk_model}")
rf"{vosk_model}"
)
self.dump_fn = None self.dump_fn = None
self.q = gevent.queue.Queue() self.q = gevent.queue.Queue()
@ -180,6 +186,7 @@ text_to_speech_service = TTS()
# endpoints # endpoints
@app.route("/stream", methods=["GET"]) @app.route("/stream", methods=["GET"])
def stream_recognition(): def stream_recognition():
def generate(): def generate():
@ -239,9 +246,14 @@ def trigger_backend_event():
try: try:
request_data = request.json request_data = request.json
message = request_data.get("message", "") message = request_data.get("message", "")
filteredMessage = re.sub(
r"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)",
"a link",
message,
)
voice = request_data.get("voice") voice = request_data.get("voice")
count = request_data.get("count") count = request_data.get("count")
text_to_speech_service.say(message, voice, count) text_to_speech_service.say(filteredMessage, voice, count)
except Exception as e: except Exception as e:
return jsonify({"error": "An error occurred"}), 500 return jsonify({"error": "An error occurred"}), 500
return jsonify({"message": "Audio triggered"}), 200 return jsonify({"message": "Audio triggered"}), 200
@ -257,14 +269,14 @@ def get_voices():
if __name__ == "__main__": if __name__ == "__main__":
LANGUAGE = LanguageDetection() # LANGUAGE = LanguageDetection()
lang = LANGUAGE.predict_lang("hola cómo estás") # lang = LANGUAGE.predict_lang("hola cómo estás")
print(lang) # print(lang)
text = "Keep it up. You are awesome" # text = "Keep it up. You are awesome"
translated = MyMemoryTranslator( # translated = MyMemoryTranslator(
source="english", target="spanish latin america" # source="english", target="spanish latin america"
).translate(text) # ).translate(text)
print(translated) # print(translated)
if len(sys.argv) > 1: if len(sys.argv) > 1:
settings.read(settingsPath) settings.read(settingsPath)
port = int(settings["GENERAL"]["PORT"]) port = int(settings["GENERAL"]["PORT"])
@ -273,5 +285,4 @@ if __name__ == "__main__":
port = 9000 port = 9000
stream_recognition() stream_recognition()
app.run(host="127.0.0.1", port=port) serve(app, host="0.0.0.0", port=port)
app.terminate()

View file

@ -1,5 +1,6 @@
@font-face { @font-face {
font-family: 'FRAMDCN'; font-family: 'FRAMDCN';
src: url(../fonts/FRAMCDN/FRAMDCN.woff);
} }
h1 { h1 {
@ -161,11 +162,15 @@ h1 {
} }
.chat-input input[good] + button { .chat-input input[good] + button {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); box-shadow:
0 0 2px rgba(0, 0, 0, 0.12),
0 2px 4px rgba(0, 0, 0, 0.24);
} }
.chat-input input[good] + button:hover { .chat-input input[good] + button:hover {
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); box-shadow:
0 8px 17px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.19);
/* filter: brightness(150%); */ /* filter: brightness(150%); */
} }
@ -202,7 +207,9 @@ h1 {
min-width: 100px; min-width: 100px;
border-radius: 5px; border-radius: 5px;
padding: 18px 5px 5px 5px; padding: 18px 5px 5px 5px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24); box-shadow:
0 0 2px rgba(0, 0, 0, 0.12),
0 2px 4px rgba(0, 0, 0, 0.24);
width: fit-content; width: fit-content;
position: relative; position: relative;
align-self: start; align-self: start;
@ -497,3 +504,12 @@ h1 {
.checkbox:checked + .toggle-small { .checkbox:checked + .toggle-small {
background-color: var(--main-color1); background-color: var(--main-color1);
} }
.emoji-picker {
margin-left: auto;
visibility: hidden;
bottom: 50px;
left: 0px;
position: absolute;
z-index: 1;
}

View file

@ -1,9 +1,9 @@
input[type="radio"]:checked { input[type='radio']:checked {
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
} }
input[type="radio"] { input[type='radio'] {
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
} }
@ -12,30 +12,30 @@ label.btn span {
font-size: 1.5em; font-size: 1.5em;
} }
label input[type="radio"]~i.fa.fa-square { label input[type='radio'] ~ i.fa.fa-square {
color: var(--main-color3); color: var(--main-color3);
display: inline; display: inline;
} }
label input[type="radio"]~i.fa.fa-check-square { label input[type='radio'] ~ i.fa.fa-check-square {
display: none; display: none;
} }
label input[type="radio"]:checked~i.fa.fa-square { label input[type='radio']:checked ~ i.fa.fa-square {
display: none; display: none;
} }
label input[type="radio"]:checked~i.fa.fa-check-square { label input[type='radio']:checked ~ i.fa.fa-check-square {
display: inline; display: inline;
color: var(--main-color2); color: var(--main-color2);
} }
label:hover input[type="radio"]~i.fa { label:hover input[type='radio'] ~ i.fa {
color: var(--main-color1); color: var(--main-color1);
/* filter: brightness(150%); */ /* filter: brightness(150%); */
} }
div[data-toggle="buttons"] label { div[data-toggle='buttons'] label {
display: inline-block; display: inline-block;
padding: 3px 12px; padding: 3px 12px;
margin-bottom: 0; margin-bottom: 0;
@ -55,8 +55,8 @@ div[data-toggle="buttons"] label {
user-select: none; user-select: none;
} }
div[data-toggle="buttons"] label:active, div[data-toggle='buttons'] label:active,
div[data-toggle="buttons"] label.active { div[data-toggle='buttons'] label.active {
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none; box-shadow: none;
} }

View file

@ -219,8 +219,18 @@ body {
display: inline-block; display: inline-block;
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, font-family:
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 'NotoColorEmojiLimited',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
@ -232,8 +242,18 @@ body {
width: 55px; width: 55px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 2; z-index: 2;
font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, font-family:
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 'NotoColorEmojiLimited',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
} }
.language-item { .language-item {
@ -242,9 +262,9 @@ body {
background-color: var(--top-bar); background-color: var(--top-bar);
} }
.language-item:hover { /* .language-item:hover {
/* filter: brightness(150%); */ filter: brightness(150%);
} } */
@font-face { @font-face {
font-family: NotoColorEmojiLimited; font-family: NotoColorEmojiLimited;

View file

@ -85,7 +85,9 @@
line-height: 1.5em; line-height: 1.5em;
font-family: Helvetica; font-family: Helvetica;
text-align: center; text-align: center;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow:
rgba(0, 0, 0, 0.16) 0px 3px 6px,
rgba(0, 0, 0, 0.23) 0px 3px 6px;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }
@ -96,7 +98,9 @@
line-height: 1.5em; line-height: 1.5em;
font-family: Helvetica; font-family: Helvetica;
text-align: center; text-align: center;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; box-shadow:
rgba(0, 0, 0, 0.16) 0px 3px 6px,
rgba(0, 0, 0, 0.23) 0px 3px 6px;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }

View file

@ -53,19 +53,27 @@ input[type='range'].styled-slider::-webkit-slider-runnable-track {
} }
input[type='range'].styled-slider.slider-progress1::-webkit-slider-runnable-track { input[type='range'].styled-slider.slider-progress1::-webkit-slider-runnable-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#1a1a1a;
} }
input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track { input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#1a1a1a;
} }
input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track { input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#1a1a1a;
} }
input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track { input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#1a1a1a;
} }
/*mozilla*/ /*mozilla*/
@ -87,19 +95,27 @@ input[type='range'].styled-slider::-moz-range-track {
} }
input[type='range'].styled-slider.slider-progress1::-moz-range-track { input[type='range'].styled-slider.slider-progress1::-moz-range-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#464646;
} }
input[type='range'].styled-slider.slider-progress2::-moz-range-track { input[type='range'].styled-slider.slider-progress2::-moz-range-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#464646;
} }
input[type='range'].styled-slider.slider-progress3::-moz-range-track { input[type='range'].styled-slider.slider-progress3::-moz-range-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#464646;
} }
input[type='range'].styled-slider.slider-progress4::-moz-range-track { input[type='range'].styled-slider.slider-progress4::-moz-range-track {
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646; background:
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#464646;
} }
/*ms*/ /*ms*/

View file

@ -152,8 +152,8 @@ input:checked + label {
bottom: 0px; bottom: 0px;
} }
.tabx-bar .tabx::after { /* .tabx-bar .tabx::after {
} } */
.tabx-bar .tabx:hover { .tabx-bar .tabx:hover {
padding-bottom: 10px; padding-bottom: 10px;
@ -325,7 +325,9 @@ input[type='lol'] {
margin: 0.5rem; margin: 0.5rem;
opacity: 0; opacity: 0;
transform: translateY(100%); transform: translateY(100%);
animation: toastAnimation 0.5s ease-in-out forwards, toastDisappear 0.5s ease-in-out 9s forwards; animation:
toastAnimation 0.5s ease-in-out forwards,
toastDisappear 0.5s ease-in-out 9s forwards;
} }
/* Apply different colors based on the toast type */ /* Apply different colors based on the toast type */
@ -385,7 +387,9 @@ input[type='lol'] {
visibility: hidden; visibility: hidden;
opacity: 1; opacity: 1;
box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2); box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2);
transition: opacity 0.3s, visibility 0s; transition:
opacity 0.3s,
visibility 0s;
color: var(--main-color2); color: var(--main-color2);
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
z-index: 999; z-index: 999;

View file

@ -74,7 +74,10 @@ textarea {
color: var(--main-color2); color: var(--main-color2);
width: 50px; width: 50px;
cursor: pointer; cursor: pointer;
text-shadow: 0 0 5px #070607, 0 0 5px #070607, 0 0 5px #070607; text-shadow:
0 0 5px #070607,
0 0 5px #070607,
0 0 5px #070607;
/* transition: all 0.15s ease-in-out; */ /* transition: all 0.15s ease-in-out; */
text-align: center; text-align: center;
} }
@ -89,7 +92,10 @@ textarea {
.SmallButton:active { .SmallButton:active {
color: var(--main-color1); color: var(--main-color1);
transform: translateY(4px); transform: translateY(4px);
text-shadow: 0 0 5px #000, 0 0 5px #000, 0 0 5px #000; text-shadow:
0 0 5px #000,
0 0 5px #000,
0 0 5px #000;
} }
.AdvancedMenuButton { .AdvancedMenuButton {
@ -103,7 +109,9 @@ textarea {
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
font-size: 14pt; font-size: 14pt;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: box-shadow 0.3s ease, background-color 0.3s ease; transition:
box-shadow 0.3s ease,
background-color 0.3s ease;
/* Add a smooth transition for box-shadow and background-color */ /* Add a smooth transition for box-shadow and background-color */
} }

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -25,6 +25,7 @@
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
/> />
<link rel="stylesheet" href="./css/logger.css" /> <link rel="stylesheet" href="./css/logger.css" />
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
<!--#endregion --> <!--#endregion -->
</head> </head>
@ -132,9 +133,9 @@
<img class="AdvancedMenuIcon" src="./images/settings.png" alt=" " /> <img class="AdvancedMenuIcon" src="./images/settings.png" alt=" " />
<div class="AdvancedMenuLabel3">General settings</div> <div class="AdvancedMenuLabel3">General settings</div>
</legend> </legend>
<div class="AdvancedMenuRow inputServer"> <div class="AdvancedMenuRow">
<div class="AdvancedMenuLabel">Port</div> <div class="AdvancedMenuLabel">Port</div>
<input type="text" class="fname inputServer" id="PORT" /> <input type="text" class="fname" id="PORT" />
<i <i
class="fa fa-question-circle fa-2x SmallButton option-icon-container" class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_PORT" id="Info_PORT"
@ -142,14 +143,40 @@
></i> ></i>
</div> </div>
<div class="AdvancedMenuRow"> <div class="AdvancedMenuRow">
<div class="AdvancedMenuLabel">Zoom level %</div>
<input type="text" class="fname" id="ZOOMLEVEL" />
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_ZOOMLEVEL"
tip="Port to use to host additional services"
></i>
</div>
</fieldset>
<fieldset id="TTSMenu" class="AdvancedMenu">
<legend class="legendStyle" tip="Enable/Disable TTS">
<img class="AdvancedMenuIcon" src="./images/tts.png" alt=" " />
<input type="checkbox" id="USE_TTS" class="checkbox" />
<label for="USE_TTS" class="toggle-small"></label>
<div class="AdvancedMenuLabel3">Enable TTS</div>
</legend>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Default TTS Service</div> <div class="AdvancedMenuLabel">Default TTS Service</div>
<select class="menu-select" name="primaryTTSService" id="primaryTTSService"></select> <select class="menu-select" name="primaryTTSService" id="primaryTTSService"></select>
</div> </div>
<div class="AdvancedMenuRow"> <div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Default TTS language</div>
<select class="menu-select" name="defaultLanguage" id="defaultLanguage" tip="Language Service to use"></select>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">2<sup>nd</sup> TTS Service</div> <div class="AdvancedMenuLabel">2<sup>nd</sup> TTS Service</div>
<select class="menu-select" name="secondaryTTSService" id="secondaryTTSService"></select> <select class="menu-select" name="secondaryTTSService" id="secondaryTTSService"></select>
</div> </div>
<div class="AdvancedMenuRow"> <div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">2<sup>nd</sup> TTS language</div>
<select class="menu-select" name="secondaryLanguage" id="secondaryLanguage" tip="Language Service to use"></select>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">TTS Output Device</div> <div class="AdvancedMenuLabel">TTS Output Device</div>
<select class="menu-select" name="ttsAudioDevice" id="ttsAudioDevice"></select> <select class="menu-select" name="ttsAudioDevice" id="ttsAudioDevice"></select>
<i <i
@ -158,7 +185,7 @@
tip="Outputting to specific device will NOT work with voicemeter" tip="Outputting to specific device will NOT work with voicemeter"
></i> ></i>
</div> </div>
<div class="AdvancedMenuRow"> <div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">TTS Volume</div> <div class="AdvancedMenuLabel">TTS Volume</div>
<div class="slider-container"> <div class="slider-container">
<input id="ttsVolumeSlider" class="styled-slider slider-progress1" type="range" /> <input id="ttsVolumeSlider" class="styled-slider slider-progress1" type="range" />
@ -168,6 +195,30 @@
<input type="text" id="ttsVolume" class="inputBox" /> <input type="text" id="ttsVolume" class="inputBox" />
</div> </div>
</div> </div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Default Internal Voice</div>
<select class="menu-select" name="primaryVoice" id="primaryVoice"></select>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Test default Internal Voice</div>
<textarea id="testPrimaryTTS">Hi, This is a test</textarea>
<div class="option-icon-container" tip="Test internal TTS">
<i class="fa fa-play-circle fa-2x SmallButton option-icon-container" id="TestDefaultTTSButton"></i>
<label class="testLabel Basiclabel"></label>
</div>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">2<sup>nd</sup> Internal Voice</div>
<select class="menu-select" name="secondaryVoice" id="secondaryVoice"></select>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Test 2<sup>nd</sup> Internal Voice</div>
<textarea id="testSecondaryTTS">Hi, This is a test</textarea>
<div class="option-icon-container" tip="Test internal TTS">
<i class="fa fa-play-circle fa-2x SmallButton option-icon-container" id="TestSecondaryTTSButton"></i>
<label class="testLabel Basiclabel"></label>
</div>
</div>
</fieldset> </fieldset>
<fieldset id="STTMenu" class="AdvancedMenu"> <fieldset id="STTMenu" class="AdvancedMenu">
@ -195,83 +246,20 @@
<div class="AdvancedMenuLabel3">Enable Language detection</div> <div class="AdvancedMenuLabel3">Enable Language detection</div>
</legend> </legend>
<div class="AdvancedMenuRow languageDetectionInput"> <div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Language detection service</div> <div class="AdvancedMenuLabel">Translate chat messages to</div>
<select <select class="menu-select" name="language" id="translateChatMessageLanguage" tip="Language Service to use"></select>
class="menu-select"
name="language"
id="languageService"
tip="Language Service to use"
></select>
</div> </div>
<div class="AdvancedMenuRow languageDetectionInput"> <div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Translate incoming chat messages to</div> <div class="AdvancedMenuLabel">Broadcast translation</div>
<label for="USE_CHAT_LANGUAGE_DETECTION" class="toggle-small"></label> <label for="USE_CHAT_LANGUAGE_DETECTION" class="toggle-small"></label>
<input type="checkbox" id="USE_CHAT_LANGUAGE_DETECTION" class="checkbox" /> <input type="checkbox" id="USE_CHAT_LANGUAGE_DETECTION" class="checkbox" />
<select
class="menu-select"
name="language"
id="translateChatMessageLanguage"
tip="Language Service to use"
></select>
</div> </div>
<div class="AdvancedMenuRow languageDetectionInput"> <div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Default TTS language</div> <div class="AdvancedMenuLabel">Output to TTS service</div>
<select <select class="menu-select" name="language" id="translateChatMessageLanguage" tip="Language Service to use"></select>
class="menu-select"
name="defaultLanguage"
id="defaultLanguage"
tip="Language Service to use"
></select>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">2<sup>nd</sup> TTS language</div>
<select
class="menu-select"
name="secondaryLanguage"
id="secondaryLanguage"
tip="Language Service to use"
></select>
</div> </div>
</fieldset> </fieldset>
<fieldset id="TTSMenu" class="AdvancedMenu">
<legend class="legendStyle" tip="Enable/Disable TTS">
<img class="AdvancedMenuIcon" src="./images/tts.png" alt=" " />
<input type="checkbox" id="USE_TTS" class="checkbox" />
<label for="USE_TTS" class="toggle-small "></label>
<div class="AdvancedMenuLabel3">Enable internal TTS</div>
</legend>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Default Internal Voice</div>
<select class="menu-select" name="primaryVoice" id="primaryVoice"></select>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Test default Internal Voice</div>
<textarea id="testPrimaryTTS">Hi, This is a test</textarea>
<div class="option-icon-container" tip="Test internal TTS">
<i
class="fa fa-play-circle fa-2x SmallButton option-icon-container"
id="TestDefaultTTSButton"
></i>
<label class="testLabel Basiclabel"></label>
</div>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">2<sup>nd</sup> Internal Voice</div>
<select class="menu-select" name="secondaryVoice" id="secondaryVoice"></select>
</div>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Test 2<sup>nd</sup> Internal Voice</div>
<textarea id="testSecondaryTTS">Hi, This is a test</textarea>
<div class="option-icon-container" tip="Test internal TTS">
<i
class="fa fa-play-circle fa-2x SmallButton option-icon-container"
id="TestSecondaryTTSButton"
></i>
<label class="testLabel Basiclabel"></label>
</div>
</div>
</fieldset>
<fieldset id="NotificationMenu" class="AdvancedMenu"> <fieldset id="NotificationMenu" class="AdvancedMenu">
<legend class="legendStyle" tip="Enable/Disable notification sounds"> <legend class="legendStyle" tip="Enable/Disable notification sounds">
<img class="AdvancedMenuIcon" src="./images/sound.png" alt=" " /> <img class="AdvancedMenuIcon" src="./images/sound.png" alt=" " />
@ -279,6 +267,16 @@
<label for="USE_NOTIFICATION_SOUNDS" class="toggle-small"></label> <label for="USE_NOTIFICATION_SOUNDS" class="toggle-small"></label>
<div class="AdvancedMenuLabel3">Enable notification sounds</div> <div class="AdvancedMenuLabel3">Enable notification sounds</div>
</legend> </legend>
<div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Notification sounds Output Device</div>
<select class="menu-select" name="notificationSoundAudioDevice" id="notificationSoundAudioDevice"></select>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_OUTPUT_NOTIFIACTION_SOUNDS"
tip="Outputting to specific device will NOT work with voicemeter"
></i>
</div>
<div class="AdvancedMenuRow inputNotificationSound"> <div class="AdvancedMenuRow inputNotificationSound">
<div class="AdvancedMenuLabel">Notification Volume</div> <div class="AdvancedMenuLabel">Notification Volume</div>
<div class="slider-container"> <div class="slider-container">
@ -293,11 +291,7 @@
<div class="AdvancedMenuLabel">Notification Sound</div> <div class="AdvancedMenuLabel">Notification Sound</div>
<select class="menu-select" name="notification" id="notification"></select> <select class="menu-select" name="notification" id="notification"></select>
<div class="option-icon-container"> <div class="option-icon-container">
<i <i class="fa fa-play-circle fa-2x SmallButton option-icon-container" id="SoundTestButton" tip="Test Sound"></i>
class="fa fa-play-circle fa-2x SmallButton option-icon-container"
id="SoundTestButton"
tip="Test Sound"
></i>
<label class="testLabel Basiclabel"></label> <label class="testLabel Basiclabel"></label>
</div> </div>
</div> </div>
@ -331,11 +325,7 @@
<button class="password-toggle-btn password-toggle-btn1"> <button class="password-toggle-btn password-toggle-btn1">
<span class="password-toggle-icon"><i class="fa-regular fa-eye-slash"></i></span> <span class="password-toggle-icon"><i class="fa-regular fa-eye-slash"></i></span>
</button> </button>
<i <i class="fa fa-question-circle fa-2x SmallButton option-icon-container" id="Info_USERNAME" tip="Get OAuth Token"></i>
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_USERNAME"
tip="Get OAuth Token"
></i>
</div> </div>
<div class="AdvancedMenuRow inputTwitch"> <div class="AdvancedMenuRow inputTwitch">
<div class="AdvancedMenuLabel">Test credentials</div> <div class="AdvancedMenuLabel">Test credentials</div>
@ -355,6 +345,17 @@
<label for="USE_MODULES" class="toggle-small"></label> <label for="USE_MODULES" class="toggle-small"></label>
<div class="AdvancedMenuLabel3">Enable Modules</div> <div class="AdvancedMenuLabel3">Enable Modules</div>
</legend> </legend>
<div class="AdvancedMenuRow inputServer">
<div class="AdvancedMenuLabel">Use PNGtuber</div>
<input type="checkbox" id="USE_PNGTUBER" class="checkbox" />
<label for="USE_PNGTUBER" class="toggle-small"></label>
<input type="url" type2="text" class="fname inputServer" id="PNGTUBER_URL" />
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_PNGTUBER"
tip="You can use it as a browsersource on http://localhost:PORT/pngtuber"
></i>
</div>
<div class="AdvancedMenuRow inputServer"> <div class="AdvancedMenuRow inputServer">
<div class="AdvancedMenuLabel">Use Vtuber</div> <div class="AdvancedMenuLabel">Use Vtuber</div>
<input type="checkbox" id="USE_VTUBER" class="checkbox" /> <input type="checkbox" id="USE_VTUBER" class="checkbox" />
@ -377,6 +378,17 @@
tip="You can use it as a browsersource on http://localhost:PORT/chat" tip="You can use it as a browsersource on http://localhost:PORT/chat"
></i> ></i>
</div> </div>
<div class="AdvancedMenuRow inputServer">
<div class="AdvancedMenuLabel">Use Finger</div>
<input type="checkbox" id="USE_CHATBUBBLE" class="checkbox" />
<label for="USE_CHATBUBBLE" class="toggle-small"></label>
<input type="url" type2="text" class="fname inputServer" id="CHATBUBBLE_URL" />
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_CHATBUBBLE"
tip="You can use it as a browsersource on http://localhost:PORT/chat"
></i>
</div>
</fieldset> </fieldset>
<fieldset id="AdvancedMenuAmazon" class="AdvancedMenu"> <fieldset id="AdvancedMenuAmazon" class="AdvancedMenu">
@ -530,6 +542,12 @@
<div id="chatBox" class="message-window"></div> <div id="chatBox" class="message-window"></div>
<!-- User input box--> <!-- User input box-->
<div id="userInput" class="chat-input"> <div id="userInput" class="chat-input">
<div id="emoji-picker" class="emoji-picker">
<emoji-picker class="dark"></emoji-picker>
</div>
<button class="SmallButton" id="emojis">
<i class="fa-regular fa-grin fa-2x" aria-hidden="true"></i>
</button>
<!-- User text input--> <!-- User text input-->
<input id="textInput" class="input-box" type="text" name="msg" placeholder="Tap 'Enter' to send a message" /> <input id="textInput" class="input-box" type="text" name="msg" placeholder="Tap 'Enter' to send a message" />

View file

@ -1,3 +1,5 @@
/* global settings, callback, addVoiceService, amazonVoices, */
const https = require('https'); const https = require('https');
const querystring = require('querystring'); const querystring = require('querystring');
const aws4 = require('aws4'); const aws4 = require('aws4');
@ -10,12 +12,12 @@ function getAmazonVoices() {
addVoiceService('Amazon'); addVoiceService('Amazon');
let primaryVoice = document.querySelector('#primaryAmazonVoice'); const primaryVoice = document.querySelector('#primaryAmazonVoice');
let secondaryVoice = document.querySelector('#secondaryAmazonVoice'); const secondaryVoice = document.querySelector('#secondaryAmazonVoice');
function setVoicesinSelect(voiceSelect) { function setVoicesinSelect(voiceSelect) {
const voices = Object.values(amazonVoices); const voices = Object.values(amazonVoices);
voices.forEach((voice) => { voices.forEach(voice => {
const option = document.createElement('option'); const option = document.createElement('option');
option.classList.add('option'); option.classList.add('option');
@ -36,8 +38,6 @@ if (settings.AMAZON.USE_AMAZON) {
} }
class PollyTTS { class PollyTTS {
constructor() {}
textToSpeech(options, callback) { textToSpeech(options, callback) {
if (!options) { if (!options) {
return callback(new Error('Options are missing')); return callback(new Error('Options are missing'));
@ -49,27 +49,27 @@ class PollyTTS {
VoiceId: options.voiceId || 'Mia', VoiceId: options.voiceId || 'Mia',
SampleRate: options.sampleRate || 22050, SampleRate: options.sampleRate || 22050,
OutputFormat: options.outputFormat || 'mp3', OutputFormat: options.outputFormat || 'mp3',
Engine: options.engine || 'neural', Engine: options.engine || 'neural'
}; };
const opts = { const opts = {
service: 'polly', service: 'polly',
region: options.region || 'us-east-1', region: options.region || 'us-east-1',
path: `/v1/speech?${querystring.stringify(qs)}`, path: `/v1/speech?${querystring.stringify(qs)}`,
signQuery: true, signQuery: true
}; };
// you can also pass AWS credentials in explicitly (otherwise taken from process.env) // you can also pass AWS credentials in explicitly (otherwise taken from process.env)
aws4.sign(opts, this.credentials); aws4.sign(opts, this.credentials);
https https
.get(opts, (res) => { .get(opts, res => {
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`));
} }
callback(null, res); callback(null, res);
return true; return true;
}) })
.on('error', (e) => { .on('error', e => {
callback(e); callback(e);
}); });
@ -79,20 +79,20 @@ class PollyTTS {
describeVoices(callback, credentials) { describeVoices(callback, credentials) {
this.credentials = credentials; this.credentials = credentials;
const qs = { const qs = {
Engine: 'neural', Engine: 'neural'
}; };
const opts = { const opts = {
service: 'polly', service: 'polly',
region: 'us-east-1', region: 'us-east-1',
path: `/v1/voices?${querystring.stringify(qs)}`, path: `/v1/voices?${querystring.stringify(qs)}`,
signQuery: true, signQuery: true
}; };
// you can also pass AWS credentials in explicitly (otherwise taken from process.env) // you can also pass AWS credentials in explicitly (otherwise taken from process.env)
aws4.sign(opts, this.credentials); aws4.sign(opts, this.credentials);
https https
.get(opts, (res) => { .get(opts, res => {
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`)); return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`));
} }
@ -107,7 +107,7 @@ class PollyTTS {
return undefined; return undefined;
}) })
.on('error', (e) => { .on('error', e => {
callback(e); callback(e);
}); });

View file

@ -1,18 +1,20 @@
/* global settings, fs, settingsPath, ini, shell, options, axios */
const twitchAuthentication = () => const twitchAuthentication = () =>
new Promise((resolve) => { new Promise(resolve => {
const http = require('http'); const http = require('http');
const redirectUri = 'http://localhost:1989/auth'; const redirectUri = 'http://localhost:1989/auth';
const scopes = ['chat:edit', 'chat:read']; const scopes = ['chat:edit', 'chat:read'];
const express = require('express'); const express = require('express');
let tempAuthServer = express(); const tempAuthServer = express();
const port = 1989; const port = 1989;
const { parse: parseQueryString } = require('querystring'); const { parse: parseQueryString } = require('querystring');
tempAuthServer.use(function (req, res, next) { tempAuthServer.use(function (req, res, next) {
if (req.url !== '/auth') { if (req.url !== '/auth') {
let token = parseQueryString(req.query.auth); const token = parseQueryString(req.query.auth);
settings.TWITCH.OAUTH_TOKEN = token['#access_token']; settings.TWITCH.OAUTH_TOKEN = token['#access_token'];
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -22,9 +24,9 @@ const twitchAuthentication = () =>
next(); next();
}); });
function stopServer() { // function stopServer() {
tempAuthServer.close(); // tempAuthServer.close();
} // }
const htmlString = ` const htmlString = `
<!DOCTYPE html> <!DOCTYPE html>
@ -62,7 +64,7 @@ const twitchAuthentication = () =>
server.listen(port, () => { server.listen(port, () => {
const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent( const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent(
redirectUri, redirectUri
)}&response_type=token&scope=${scopes.join(' ')}`; )}&response_type=token&scope=${scopes.join(' ')}`;
shell.openExternal(authURL); shell.openExternal(authURL);
}); });
@ -76,28 +78,140 @@ function getTwitchUserId() {
// Get user Logo with access token // Get user Logo with access token
options = { options = {
method: 'GET', method: 'GET',
url: `https://api.twitch.tv/helix/users`, url: 'https://api.twitch.tv/helix/users',
headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }, headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
}; };
axios axios
.request(options) .request(options)
.then((responseLogoUrl) => { .then(responseLogoUrl => {
console.log(responseLogoUrl.data.data[0]); console.log(responseLogoUrl.data.data[0]);
settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name; settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name;
settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url; settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url;
settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id; settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
}) })
.catch((error) => { .catch(error => {
console.error(error); console.error(error);
}); });
} }
function getTwitchOauthToken() {
return twitchAuthentication().then((res) => { function getTwitchSubscriptions(channelId) {
getTwitchUserId(); // Get user Logo with access token
return res; options = {
method: 'GET',
url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channelId}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
console.log(responseLogoUrl);
})
.catch(error => {
console.error(error);
}); });
} }
function getTwitchChannelEmotes(channelId) {
// Get user Logo with access token
options = {
method: 'GET',
url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channelId}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
console.log(responseLogoUrl);
})
.catch(error => {
console.error(error);
});
}
function getTwitchChannelFollows(channelId) {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://api.twitch.tv/helix/streams/followed',
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
console.log(responseLogoUrl);
})
.catch(error => {
console.error(error);
});
}
function getTwitchChannelSubscriptions(channelId) {
// Get user Logo with access token
options = {
method: 'GET',
url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channelId}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
console.log(responseLogoUrl);
})
.catch(error => {
console.error(error);
});
}
function getTwitchChannelId() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://api.twitch.tv/helix/users?login=MelvyCoral',
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
// console.log(responseLogoUrl);
getTwitchChannelEmotes(responseLogoUrl.data.data[0].id);
})
.catch(error => {
console.error(error);
});
}
function getTwitchOauthToken() {
// getTwitchChannelId();
getTwitchGLobalEmotes();
// return twitchAuthentication().then(res => {
// getTwitchUserId();
// return res;
// });
}
module.exports = { getTwitchOauthToken }; module.exports = { getTwitchOauthToken };

View file

@ -1,5 +1,7 @@
/* global settings, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
var kill = require('kill-process-by-name'); const kill = require('kill-process-by-name');
let python; let python;
async function getInstalledVoices() { async function getInstalledVoices() {
@ -21,12 +23,12 @@ async function getInstalledVoices() {
console.error('Error sending termination signal:', error); console.error('Error sending termination signal:', error);
} }
let primaryVoice = document.querySelector('#primaryVoice'); const primaryVoice = document.querySelector('#primaryVoice');
let secondaryVoice = document.querySelector('#secondaryVoice'); const secondaryVoice = document.querySelector('#secondaryVoice');
function setVoicesinSelect(voiceSelect) { function setVoicesinSelect(voiceSelect) {
const voices = Object.values(internalVoices.voices); const voices = Object.values(internalVoices.voices);
voices.forEach((voice) => { voices.forEach(voice => {
const option = document.createElement('option'); const option = document.createElement('option');
option.classList.add('option'); option.classList.add('option');
@ -43,6 +45,7 @@ async function getInstalledVoices() {
} }
async function getBackendServerStatus() { async function getBackendServerStatus() {
console.log('getting status');
try { try {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' }); const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' });
if (response.ok) { if (response.ok) {
@ -59,12 +62,12 @@ async function getBackendServerStatus() {
function startSTT() { function startSTT() {
const eventSource = new EventSource('http://127.0.0.1:9000/stream'); const eventSource = new EventSource('http://127.0.0.1:9000/stream');
eventSource.addEventListener('message', (event) => { eventSource.addEventListener('message', event => {
const result = event.data; const result = event.data;
console.log(result); // Log the received data console.log(result); // Log the received data
}); });
eventSource.addEventListener('error', (event) => { eventSource.addEventListener('error', event => {
console.error('EventSource failed:', event); console.error('EventSource failed:', event);
eventSource.close(); eventSource.close();
@ -81,9 +84,9 @@ async function getInternalTTSAudio(requestData) {
const requestOptions = { const requestOptions = {
method: 'POST', // HTTP method method: 'POST', // HTTP method
headers: { headers: {
'Content-Type': 'application/json', // Specify the content type 'Content-Type': 'application/json' // Specify the content type
}, },
body: JSON.stringify(requestData), // Convert the data to JSON and include it in the request body body: JSON.stringify(requestData) // Convert the data to JSON and include it in the request body
}; };
try { try {
@ -101,32 +104,40 @@ async function getInternalTTSAudio(requestData) {
} }
const createBackendServer = () => const createBackendServer = () =>
new Promise((resolve) => { new Promise(resolve => {
if (main.isPackaged) { if (main.isPackaged) {
python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']); python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']);
} else { } else {
python = spawn('python', ['-u', path.join(pythonPath, './loquendoBot_backend.py'), settingsPath, 'dev']); python = spawn('python', ['-u', path.join(pythonPath, './loquendoBot_backend.py'), settingsPath, 'dev']);
} }
// Capture the stdout of the Python process // Capture the stdout of the Python process
python.stdout.on('data', (data) => { python.stdout.on('data', data => {
console.info(`${data}`); console.info(`${data}`);
if (data.toString().startsWith('kees')) {
console.log('yess');
// getBackendServerStatus();
}
}); });
// Capture the stderr of the Python process // Capture the stderr of the Python process
python.stderr.on('data', (data) => { python.stderr.on('data', data => {
// console.error(`${data}`);
if (data.toString().startsWith('INFO:waitress:Serving on')) {
resolve('finished');
} else {
console.error(`${data}`); console.error(`${data}`);
resolve('finished'); // cannot get it to resolve with stdout }
}); });
// Listen for the Python process to exit // Listen for the Python process to exit
python.on('close', (code) => { python.on('close', code => {
console.log(`Python process exited with code ${code}`); console.log(`Python process exited with code ${code}`);
}); });
if (typeof python.pid !== 'number') { if (typeof python.pid !== 'number') {
console.log('failed'); console.log('failed');
} else { } else {
console.log(`Spawned subprocess correctly!, PID = ${python.pid}`); // console.log(`Spawned subprocess correctly!, PID = ${python.pid}`);
} }
}); });

View file

@ -1,4 +1,6 @@
function getResponse() { /* global messageTemplates, emojiPicker, settings, getPostTime, showChatMessage, twitch */
async function getResponse() {
const userText = document.querySelector('#textInput').value; const userText = document.querySelector('#textInput').value;
// If nothing is written don't do anything // If nothing is written don't do anything
@ -27,20 +29,40 @@ function getResponse() {
const msg = article.querySelector('.msg-box'); const msg = article.querySelector('.msg-box');
if (msg) { if (msg) {
msg.innerText = userText; console.log(0);
} await replaceChatMessageWithCustomEmojis(userText).then(data => {
// console.log(data);
msg.innerHTML = data;
// Appends the message to the main chat box (shows the message) // Appends the message to the main chat box (shows the message)
showChatMessage(article, true); showChatMessage(article);
twitch.sendMessage(userText); twitch.sendMessage(userText);
// Empty input box after sending message // Empty input box after sending message
document.body.querySelector('#textInput').value = ''; document.body.querySelector('#textInput').value = '';
});
}
} }
const replaceChatMessageWithCustomEmojis = message =>
new Promise(resolve => {
const words = message.split(' ');
words.forEach(async word => {
if (word !== '') {
await emojiPicker.database.getEmojiByUnicodeOrName(word).then(data => {
if (data && data.name === word) {
const url = `<img src="${data.url}">`;
message = message.replace(word, url);
}
});
resolve(message);
}
});
});
// Function that will execute when you press 'enter' in the message box // Function that will execute when you press 'enter' in the message box
document.body.querySelector('#textInput').addEventListener('keydown', (e) => { document.body.querySelector('#textInput').addEventListener('keydown', e => {
if (e.which === 13) { if (e.which === 13) {
getResponse(); getResponse();
} }
@ -86,20 +108,19 @@ const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
btn.addEventListener( btn.addEventListener(
'click', 'click',
(event) => { event => {
event.stopPropagation(); event.stopPropagation();
panels.forEach((el) => { panels.forEach(el => {
if (el === panel) return; if (el === panel) return;
el.classList.remove('show'); el.classList.remove('show');
}); });
if (panel.classList.contains('show')) { if (!panel.classList.contains('show')) {
} else {
panel.classList.add('show'); panel.classList.add('show');
} }
}, },
{ {
capture: true, capture: true
}, }
); );
}; };
@ -119,20 +140,19 @@ const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
btn.addEventListener( btn.addEventListener(
'click', 'click',
(event) => { event => {
event.stopPropagation(); event.stopPropagation();
panels.forEach((el) => { panels.forEach(el => {
if (el === panel) return; if (el === panel) return;
el.classList.remove('item-active'); el.classList.remove('item-active');
}); });
if (panel.classList.contains('item-active')) { if (!panel.classList.contains('item-active')) {
} else {
panel.classList.add('item-active'); panel.classList.add('item-active');
} }
}, },
{ {
capture: true, capture: true
}, }
); );
}; };

9
src/js/customEmojis.js Normal file
View file

@ -0,0 +1,9 @@
const customEmojis = [
{
name: 'sakuraestaKleefeliz',
shortcodes: ['sakuraestaKleefeliz'],
url: 'https://static-cdn.jtvnw.net/emoticons/v2/emotesv2_0cb536ddb6e143ab87ffeccb160a4d45/default/dark/1.0'
}
];
module.exports = { customEmojis };

View file

@ -1,3 +1,5 @@
/* global settings, addVoiceService, googleVoices */
function getGoogleVoices() { function getGoogleVoices() {
if (!settings.GOOGLE.USE_GOOGLE) { if (!settings.GOOGLE.USE_GOOGLE) {
return; return;
@ -5,12 +7,12 @@ function getGoogleVoices() {
addVoiceService('Google'); addVoiceService('Google');
let primaryVoice = document.querySelector('#primaryGoogleVoice'); const primaryVoice = document.querySelector('#primaryGoogleVoice');
let secondaryVoice = document.querySelector('#secondaryGoogleVoice'); const secondaryVoice = document.querySelector('#secondaryGoogleVoice');
function setVoicesinSelect(voiceSelect) { function setVoicesinSelect(voiceSelect) {
const voices = Object.values(googleVoices); const voices = Object.values(googleVoices);
voices.forEach((voice) => { voices.forEach(voice => {
const option = document.createElement('option'); const option = document.createElement('option');
option.classList.add('option'); option.classList.add('option');

View file

@ -4,6 +4,7 @@
// *info page with credits, version and more info // *info page with credits, version and more info
const languages = { const languages = {
none: { IETF: 'none', 'ISO-639': 'none' },
acehnese: { IETF: 'ace-ID', 'ISO-639': 'ace' }, acehnese: { IETF: 'ace-ID', 'ISO-639': 'ace' },
afrikaans: { IETF: 'af-ZA', 'ISO-639': 'af' }, afrikaans: { IETF: 'af-ZA', 'ISO-639': 'af' },
akan: { IETF: 'ak-GH', 'ISO-639': 'ak' }, akan: { IETF: 'ak-GH', 'ISO-639': 'ak' },
@ -325,7 +326,7 @@ const languages = {
xhosa: { IETF: 'xh-ZA', 'ISO-639': 'xh' }, xhosa: { IETF: 'xh-ZA', 'ISO-639': 'xh' },
yiddish: { IETF: 'yi-YD', 'ISO-639': 'yi' }, yiddish: { IETF: 'yi-YD', 'ISO-639': 'yi' },
yoruba: { IETF: 'yo-NG', 'ISO-639': 'yo' }, yoruba: { IETF: 'yo-NG', 'ISO-639': 'yo' },
zulu: { IETF: 'zu-ZA', 'ISO-639': 'zu' }, zulu: { IETF: 'zu-ZA', 'ISO-639': 'zu' }
}; };
module.exports = { languages }; module.exports = { languages };

View file

@ -9,13 +9,13 @@ const consoleFormat = format.combine(
format.colorize(), format.colorize(),
format.timestamp(), format.timestamp(),
format.align(), format.align(),
format.printf((info) => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`), format.printf(info => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`)
); );
const fileFormat = format.combine( const fileFormat = format.combine(
format.timestamp(), format.timestamp(),
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }), format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }),
format.json(), format.json()
); );
const logger = createLogger({ const logger = createLogger({
@ -24,32 +24,32 @@ const logger = createLogger({
transports: [ transports: [
new transports.File({ new transports.File({
filename: path.join(__dirname, '../logs/error.log'), filename: path.join(__dirname, '../logs/error.log'),
level: 'error', level: 'error'
}), }),
new transports.File({ new transports.File({
filename: path.join(__dirname, '../logs/activity.log'), filename: path.join(__dirname, '../logs/activity.log'),
maxsize: 5242880, maxsize: 5242880,
maxFiles: 5, maxFiles: 5
}), })
], ]
}); });
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
logger.add( logger.add(
new transports.Console({ new transports.Console({
level: consoleloggerLevel, level: consoleloggerLevel,
format: consoleFormat, format: consoleFormat
}), })
); );
} }
fetch(path.join(__dirname, '../logs/activity.log')) fetch(path.join(__dirname, '../logs/activity.log'))
.then((response) => response.text()) .then(response => response.text())
.then((logData) => { .then(logData => {
const logLines = logData.trim().split('\n'); const logLines = logData.trim().split('\n');
const tableBody = document.getElementById('logContent'); const tableBody = document.getElementById('logContent');
logLines.forEach((logLine) => { logLines.forEach(logLine => {
const logObject = JSON.parse(logLine); const logObject = JSON.parse(logLine);
const row = document.createElement('tr'); const row = document.createElement('tr');
@ -73,6 +73,8 @@ fetch(path.join(__dirname, '../logs/activity.log'))
tableBody.appendChild(row); tableBody.appendChild(row);
}); });
}) })
.catch((error) => {}); .catch(error => {
console.error(error);
});
module.exports = logger; module.exports = logger;

View file

@ -1,15 +1,17 @@
let micSelect = document.querySelector('#microphone'); /* global settings, */
const micSelect = document.querySelector('#microphone');
let selectedMic; let selectedMic;
function getAvailableMediaDevices(type) { function getAvailableMediaDevices(type) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
navigator.mediaDevices navigator.mediaDevices
.enumerateDevices() .enumerateDevices()
.then((devices) => { .then(devices => {
const microphones = devices.filter((device) => device.kind === type); const microphones = devices.filter(device => device.kind === type);
resolve(microphones); resolve(microphones);
}) })
.catch((error) => { .catch(error => {
reject(error); reject(error);
}); });
}); });
@ -17,10 +19,10 @@ function getAvailableMediaDevices(type) {
// Microphones // Microphones
getAvailableMediaDevices('audioinput') getAvailableMediaDevices('audioinput')
.then((microphones) => { .then(microphones => {
let i = 0; let i = 0;
let tempname = ''; let tempname = '';
for (let mic of microphones) { for (const mic of microphones) {
if (mic.deviceId === 'default') { if (mic.deviceId === 'default') {
tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name. tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name.
} }
@ -44,6 +46,6 @@ getAvailableMediaDevices('audioinput')
i++; i++;
} }
}) })
.catch((error) => { .catch(error => {
console.error('Error retrieving microphones:', error); console.error('Error retrieving microphones:', error);
}); });

View file

@ -3,7 +3,7 @@ const ini = require('ini');
const path = require('path'); // get directory path const path = require('path'); // get directory path
const axios = require('axios'); const axios = require('axios');
const { ipcRenderer, shell } = require('electron'); // necessary electron libraries to send data to the app const { webFrame, ipcRenderer, shell } = require('electron'); // necessary electron libraries to send data to the app
const io = require('socket.io-client'); const io = require('socket.io-client');
const util = require('util'); const util = require('util');
@ -17,8 +17,8 @@ const { Socket } = require('socket.io-client');
const main = ipcRenderer.sendSync('environment'); const main = ipcRenderer.sendSync('environment');
const resourcesPath = main.resourcesPath; const resourcesPath = main.resourcesPath;
let settingsPath = main.settingsPath.toString(); const settingsPath = main.settingsPath.toString();
let pythonPath = main.pythonPath.toString(); const pythonPath = main.pythonPath.toString();
const settings = main.settings; const settings = main.settings;
// TODO: remove gooogle voices txt and use api instead // TODO: remove gooogle voices txt and use api instead
@ -35,6 +35,8 @@ const devicesDropdown = document.querySelector('#devicesDropdown');
const notificationSound = document.querySelector('#notification'); // obtain the html reference of the sound comboBox const notificationSound = document.querySelector('#notification'); // obtain the html reference of the sound comboBox
const sttModel = document.querySelector('#sttModel'); // obtain the html reference of the sound comboBox const sttModel = document.querySelector('#sttModel'); // obtain the html reference of the sound comboBox
const ttsAudioDevices = document.querySelector('#ttsAudioDevice'); // obtain the html reference of the installedTTS comboBox const ttsAudioDevices = document.querySelector('#ttsAudioDevice'); // obtain the html reference of the installedTTS comboBox
const notificationSoundAudioDevices = document.querySelector('#notificationSoundAudioDevice'); // obtain the html reference of the installedTTS comboBox
const emojiPicker = document.body.querySelector('emoji-picker');
// laod local javascript files // laod local javascript files
const chat = require(path.join(__dirname, './js/chat')); const chat = require(path.join(__dirname, './js/chat'));
@ -47,18 +49,18 @@ const config = require(path.join(__dirname, './js/settings'));
const mediaDevices = require(path.join(__dirname, './js/mediaDevices')); const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
let notificationSounds = path.join(__dirname, './sounds/notifications'); const notificationSounds = path.join(__dirname, './sounds/notifications');
let sttModels = path.join(__dirname, '../speech_to_text_models'); const sttModels = path.join(__dirname, '../speech_to_text_models');
function reset() { function reset() {
ipcRenderer.send('restart'); ipcRenderer.send('restart');
} }
let server = require(path.join(__dirname, './js/server')); const server = require(path.join(__dirname, './js/server'));
const backend = require(path.join(__dirname, './js/backend')); const backend = require(path.join(__dirname, './js/backend'));
let socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server const socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server
let twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : ''; const twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
const Polly = settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : ''; const Polly = settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : '';
const google = settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/google')) : ''; const google = settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/google')) : '';
@ -66,6 +68,9 @@ const theme = require(path.join(__dirname, './js/theme'));
const auth = require(path.join(__dirname, './js/auth')); const auth = require(path.join(__dirname, './js/auth'));
let ttsRequestCount = 0; let ttsRequestCount = 0;
ttsRequestCount = 0;
let customEmojis = [];
customEmojis = [];
// initialize values // initialize values
config.getGeneralSettings(); config.getGeneralSettings();
@ -78,11 +83,15 @@ const speakButton = document.querySelector('#speakBtn');
const amazonCredentials = { const amazonCredentials = {
accessKeyId: settings.AMAZON.ACCESS_KEY, accessKeyId: settings.AMAZON.ACCESS_KEY,
secretAccessKey: settings.AMAZON.ACCESS_SECRET, secretAccessKey: settings.AMAZON.ACCESS_SECRET
}; };
// Check for installed sounds // Check for installed sounds
fs.readdir(notificationSounds, (err, files) => { fs.readdir(notificationSounds, (err, files) => {
if (err) {
console.error(err);
}
files.forEach((file, i) => { files.forEach((file, i) => {
// Create a new option element. // Create a new option element.
const option = document.createElement('option'); const option = document.createElement('option');
@ -101,7 +110,11 @@ fs.readdir(notificationSounds, (err, files) => {
// Check for installed stt models // Check for installed stt models
fs.readdir(sttModels, (err, files) => { fs.readdir(sttModels, (err, files) => {
for (let file of files) { if (err) {
console.error(err);
}
for (const file of files) {
if (file.includes('.txt')) { if (file.includes('.txt')) {
continue; continue;
} }
@ -126,25 +139,27 @@ async function getAudioDevices() {
} }
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
const audioOutputDevices = devices.filter((device) => device.kind === 'audiooutput'); const audioOutputDevices = devices.filter(device => device.kind === 'audiooutput');
audioOutputDevices.forEach((device) => { audioOutputDevices.forEach(device => {
const option = document.createElement('option'); const option = document.createElement('option');
option.text = device.label || `Output ${device.deviceId}`; option.text = device.label || `Output ${device.deviceId}`;
option.value = device.deviceId; option.value = device.deviceId;
ttsAudioDevices.appendChild(option); ttsAudioDevices.appendChild(option);
notificationSoundAudioDevices.appendChild(option);
}); });
ttsAudioDevices.selectedIndex = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE; ttsAudioDevices.selectedIndex = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE;
notificationSoundAudioDevices.selectedIndex = settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE;
} }
getAudioDevices(); getAudioDevices();
function setLanguagesinSelect(languageSelector, setting) { function setLanguagesinSelect(languageSelector, setting) {
let languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
for (const language in languageObject.languages) { for (const language in languageObject.languages) {
if (languageObject.languages.hasOwnProperty(language)) { if (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) {
const iso639 = languageObject.languages[language]['ISO-639']; const iso639 = languageObject.languages[language]['ISO-639'];
const option = document.createElement('option'); const option = document.createElement('option');
option.value = iso639; option.value = iso639;
@ -162,7 +177,7 @@ setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_I
function addVoiceService(name) { function addVoiceService(name) {
function addToselect(select) { function addToselect(select) {
let ttsService = document.querySelector(select); const ttsService = document.querySelector(select);
const option = document.createElement('option'); const option = document.createElement('option');
ttsService.appendChild(option); ttsService.appendChild(option);
@ -174,7 +189,7 @@ function addVoiceService(name) {
} }
// Small tooltip // Small tooltip
Array.from(document.body.querySelectorAll('[tip]')).forEach((el) => { Array.from(document.body.querySelectorAll('[tip]')).forEach(el => {
const tip = document.createElement('div'); const tip = document.createElement('div');
const body = document.querySelector('.container'); const body = document.querySelector('.container');
const element = el; const element = el;
@ -185,33 +200,21 @@ Array.from(document.body.querySelectorAll('[tip]')).forEach((el) => {
el.hasAttribute('tip-top') ? '-100%' : '15px' el.hasAttribute('tip-top') ? '-100%' : '15px'
})`; })`;
body.appendChild(tip); body.appendChild(tip);
element.onmousemove = (e) => { element.onmousemove = e => {
tip.style.left = `${e.x}px`; tip.style.left = `${e.x}px`;
tip.style.top = `${e.y}px`; tip.style.top = `${e.y}px`;
tip.style.zIndex = 1; tip.style.zIndex = 1;
tip.style.visibility = 'visible'; tip.style.visibility = 'visible';
}; };
element.onmouseleave = (e) => { element.onmouseleave = e => {
tip.style.visibility = 'hidden'; tip.style.visibility = 'hidden';
}; };
}); });
function showChatMessage(article, isUser) { function showChatMessage(article) {
document.querySelector('#chatBox').appendChild(article); document.querySelector('#chatBox').appendChild(article);
let usernameHtml;
let msg;
let messages = Array.from(document.body.querySelectorAll('.msg-container'));
if (isUser) { const messages = Array.from(document.body.querySelectorAll('.msg-container'));
usernameHtml = article.querySelector('.username-user');
msg = article.querySelector('.msg-box-user');
} else {
usernameHtml = article.querySelector('.username');
msg = article.querySelector('.msg-box');
}
// var style = getComputedStyle(usernameHtml);
// var style2 = getComputedStyle(usernameHtml);
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
lastMessage.scrollIntoView({ behavior: 'smooth' }); lastMessage.scrollIntoView({ behavior: 'smooth' });
@ -221,7 +224,7 @@ function getPostTime() {
const date = new Date(); const date = new Date();
document.body.querySelectorAll('.container').innerHTML = date.getHours(); document.body.querySelectorAll('.container').innerHTML = date.getHours();
const hours = date.getHours(); const hours = date.getHours();
var ampm = hours >= 12 ? 'PM' : 'AM'; const ampm = hours >= 12 ? 'PM' : 'AM';
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
const time = `${hours}:${minutes} ${ampm}`; const time = `${hours}:${minutes} ${ampm}`;
@ -253,3 +256,42 @@ hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN');
hideText('.password-toggle-btn4', '#AMAZON_ACCESS_KEY'); hideText('.password-toggle-btn4', '#AMAZON_ACCESS_KEY');
hideText('.password-toggle-btn5', '#AMAZON_ACCESS_SECRET'); hideText('.password-toggle-btn5', '#AMAZON_ACCESS_SECRET');
hideText('.password-toggle-btn6', '#GOOGLE_API_KEY'); hideText('.password-toggle-btn6', '#GOOGLE_API_KEY');
function setZoomLevel(currentZoom, zoomIn) {
let newZoom = currentZoom.toFixed(2);
if (zoomIn === true && currentZoom < 4.95) {
newZoom = (currentZoom + 0.05).toFixed(2);
}
if (zoomIn === false && currentZoom > 0.25) {
newZoom = (currentZoom - 0.05).toFixed(2);
}
webFrame.setZoomFactor(parseFloat(newZoom));
settings.GENERAL.ZOOMLEVEL = newZoom;
fs.writeFileSync(settingsPath, ini.stringify(settings));
document.body.querySelector('#ZOOMLEVEL').value = (settings.GENERAL.ZOOMLEVEL * 100).toFixed(0);
}
// const customEmojix = [
// {
// name: 'sakuraestaKleefeliz',
// shortcodes: ['sakuraestaKleefeliz'],
// url: 'https://static-cdn.jtvnw.net/emoticons/v2/emotesv2_0cb536ddb6e143ab87ffeccb160a4d45/default/dark/1.0',
// category: 'Sakura'
// }
// ];
// const customEmojiy = [
// {
// name: 'sakuraestaKleefeliz',
// shortcodes: ['sakuraestaKleefeliz'],
// url: 'https://static-cdn.jtvnw.net/emoticons/v2/emotesv2_0cb536ddb6e143ab87ffeccb160a4d45/default/dark/1.0',
// category: 'Sakurax'
// }
// ];
// emojiPicker.customEmoji = customEmojix;
// emojiPicker.customEmoji = customEmojiy;
// console.log(emojiPicker.database.getEmojiBySearchQuery('Kappa'));

View file

@ -2,16 +2,19 @@ function getBotResponse(input) {
// rock paper scissors // rock paper scissors
if (input === 'rock') { if (input === 'rock') {
return 'paper'; return 'paper';
} if (input === 'paper') { }
if (input === 'paper') {
return 'scissors'; return 'scissors';
} if (input === 'scissors') { }
if (input === 'scissors') {
return 'rock'; return 'rock';
} }
// Simple responses // Simple responses
if (input === 'hello') { if (input === 'hello') {
return 'Hello there!'; return 'Hello there!';
} if (input === 'goodbye') { }
if (input === 'goodbye') {
return 'Talk to you later!'; return 'Talk to you later!';
} }
return 'Try asking something else!'; return 'Try asking something else!';

View file

@ -1,3 +1,5 @@
/* global settings */
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const path = require('path'); const path = require('path');
@ -5,7 +7,7 @@ const http = require('http');
const localServer = http.createServer(app); const localServer = http.createServer(app);
const io = require('socket.io')(localServer); const io = require('socket.io')(localServer);
let requestCount = 0; const requestCount = 0;
function startVtuberModule() { function startVtuberModule() {
if (!settings.MODULES.USE_VTUBER) { if (!settings.MODULES.USE_VTUBER) {
@ -14,8 +16,8 @@ function startVtuberModule() {
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/'))); app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
let vtuber = document.body.querySelector('#BrowsersourceVtuber'); const vtuber = document.body.querySelector('#BrowsersourceVtuber');
let vtuberframe = document.createElement('iframe'); const vtuberframe = document.createElement('iframe');
vtuberframe.class = 'frame'; vtuberframe.class = 'frame';
vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`; vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`;
vtuberframe.style.width = '100%'; vtuberframe.style.width = '100%';
@ -33,8 +35,8 @@ function startChatBubbleModule() {
app.use('/chat', express.static(path.join(__dirname, '../modules/chat'))); app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
let chat = document.body.querySelector('#BrowsersourceChat'); const chat = document.body.querySelector('#BrowsersourceChat');
let chatframe = document.createElement('iframe'); const chatframe = document.createElement('iframe');
chatframe.class = 'frame'; chatframe.class = 'frame';
chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`; chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`;
chatframe.style.width = '100%'; chatframe.style.width = '100%';
@ -61,15 +63,12 @@ app.use((req, res, next) => {
localServer.listen(settings.GENERAL.PORT, () => { localServer.listen(settings.GENERAL.PORT, () => {
startVtuberModule(); startVtuberModule();
startChatBubbleModule(); startChatBubbleModule();
if (settings.TTS.USE_TTS) {
}
}); });
// Handle socket connections // Handle socket connections
io.on('connection', (socket) => { io.on('connection', socket => {
// Receive data from the client // Receive data from the client
socket.on('message', (data) => {}); socket.on('message', data => {});
// Receive data from the client // Receive data from the client
socket.on('xxx', (logoUrl, username, message) => { socket.on('xxx', (logoUrl, username, message) => {

View file

@ -1,7 +1,10 @@
/* global settings, setZoomLevel, webFrame, theme, fs, settingsPath, ini, startVoiceRecognition,notificationSoundAudioDevices, ttsAudioDevices, notificationSound, path, resourcesPath, ipcRenderer, auth, shell, sound, twitch, server, backend */
function getGeneralSettings() { function getGeneralSettings() {
// General // General
document.body.querySelector('#PORT').value = settings.GENERAL.PORT; document.body.querySelector('#PORT').value = settings.GENERAL.PORT;
document.body.querySelector('#ZOOMLEVEL').value = settings.GENERAL.ZOOMLEVEL * 100;
webFrame.setZoomFactor(parseFloat(settings.GENERAL.ZOOMLEVEL));
// Theme // Theme
document.querySelector('#USE_CUSTOM_THEME').value = settings.THEME.USE_CUSTOM_THEME; document.querySelector('#USE_CUSTOM_THEME').value = settings.THEME.USE_CUSTOM_THEME;
document.body.querySelector('#USE_CUSTOM_THEME').checked = settings.THEME.USE_CUSTOM_THEME === true ? 1 : 0; document.body.querySelector('#USE_CUSTOM_THEME').checked = settings.THEME.USE_CUSTOM_THEME === true ? 1 : 0;
@ -44,42 +47,42 @@ function getGeneralSettings() {
} }
document.body.querySelector('#primaryAmazonVoice').addEventListener('change', () => { document.body.querySelector('#primaryAmazonVoice').addEventListener('change', () => {
var select = document.querySelector('#primaryAmazonVoice'); const select = document.querySelector('#primaryAmazonVoice');
settings.AMAZON.PRIMARY_VOICE = select.value; settings.AMAZON.PRIMARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved Amazon primary voice!', 'success'); createNotification('Saved Amazon primary voice!', 'success');
}); });
document.body.querySelector('#secondaryAmazonVoice').addEventListener('change', () => { document.body.querySelector('#secondaryAmazonVoice').addEventListener('change', () => {
var select = document.querySelector('#secondaryAmazonVoice'); const select = document.querySelector('#secondaryAmazonVoice');
settings.AMAZON.SECONDARY_VOICE = select.value; settings.AMAZON.SECONDARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved Amazon secondary voice!', 'success'); createNotification('Saved Amazon secondary voice!', 'success');
}); });
document.body.querySelector('#primaryGoogleVoice').addEventListener('change', () => { document.body.querySelector('#primaryGoogleVoice').addEventListener('change', () => {
var select = document.querySelector('#primaryGoogleVoice'); const select = document.querySelector('#primaryGoogleVoice');
settings.GOOGLE.PRIMARY_VOICE = select.value; settings.GOOGLE.PRIMARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved Google primary voice!', 'success'); createNotification('Saved Google primary voice!', 'success');
}); });
document.body.querySelector('#secondaryGoogleVoice').addEventListener('change', () => { document.body.querySelector('#secondaryGoogleVoice').addEventListener('change', () => {
var select = document.querySelector('#secondaryGoogleVoice'); const select = document.querySelector('#secondaryGoogleVoice');
settings.GOOGLE.SECONDARY_VOICE = select.value; settings.GOOGLE.SECONDARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved Google secondary voice!', 'success'); createNotification('Saved Google secondary voice!', 'success');
}); });
document.body.querySelector('#primaryVoice').addEventListener('change', () => { document.body.querySelector('#primaryVoice').addEventListener('change', () => {
var select = document.querySelector('#primaryVoice'); const select = document.querySelector('#primaryVoice');
settings.TTS.PRIMARY_VOICE = select.value; settings.TTS.PRIMARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved primary voice!', 'success'); createNotification('Saved primary voice!', 'success');
}); });
document.body.querySelector('#microphone').addEventListener('change', () => { document.body.querySelector('#microphone').addEventListener('change', () => {
var select = document.querySelector('#microphone'); const select = document.querySelector('#microphone');
settings.STT.MICROPHONE = select.value; settings.STT.MICROPHONE = select.value;
settings.STT.MICROPHONE_ID = select.options[select.selectedIndex].text; settings.STT.MICROPHONE_ID = select.options[select.selectedIndex].text;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -88,14 +91,14 @@ document.body.querySelector('#microphone').addEventListener('change', () => {
}); });
document.body.querySelector('#sttModel').addEventListener('change', () => { document.body.querySelector('#sttModel').addEventListener('change', () => {
var select = document.querySelector('#sttModel'); const select = document.querySelector('#sttModel');
settings.STT.LANGUAGE = select.value; settings.STT.LANGUAGE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved voice detection language!', 'success'); createNotification('Saved voice detection language!', 'success');
}); });
document.body.querySelector('#defaultLanguage').addEventListener('change', () => { document.body.querySelector('#defaultLanguage').addEventListener('change', () => {
var select = document.querySelector('#defaultLanguage'); const select = document.querySelector('#defaultLanguage');
settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX = select.selectedIndex; settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX = select.selectedIndex;
settings.TTS.PRIMARY_TTS_LANGUAGE = select.options[select.selectedIndex].text; settings.TTS.PRIMARY_TTS_LANGUAGE = select.options[select.selectedIndex].text;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -103,14 +106,14 @@ document.body.querySelector('#defaultLanguage').addEventListener('change', () =>
}); });
document.body.querySelector('#secondaryVoice').addEventListener('change', () => { document.body.querySelector('#secondaryVoice').addEventListener('change', () => {
var select = document.querySelector('#secondaryVoice'); const select = document.querySelector('#secondaryVoice');
settings.TTS.SECONDARY_VOICE = select.value; settings.TTS.SECONDARY_VOICE = select.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved secondary voice!', 'success'); createNotification('Saved secondary voice!', 'success');
}); });
document.body.querySelector('#secondaryLanguage').addEventListener('change', () => { document.body.querySelector('#secondaryLanguage').addEventListener('change', () => {
var select = document.querySelector('#secondaryLanguage'); const select = document.querySelector('#secondaryLanguage');
settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX = select.selectedIndex; settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX = select.selectedIndex;
settings.TTS.SECONDARY_TTS_LANGUAGE = select.options[select.selectedIndex].text; settings.TTS.SECONDARY_TTS_LANGUAGE = select.options[select.selectedIndex].text;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -124,11 +127,18 @@ document.body.querySelector('#ttsAudioDevice').addEventListener('change', () =>
createNotification('Saved audio device!', 'success'); createNotification('Saved audio device!', 'success');
}); });
document.body.querySelector('#notificationSoundAudioDevice').addEventListener('change', () => {
settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE = notificationSoundAudioDevices.value;
settings.AUDIO.NOTIFICATION_AUDIO_DEVICE = notificationSoundAudioDevices.selectedIndex;
fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved audio device!', 'success');
});
document.body.querySelector('#TWITCH_CHANNEL_NAME').addEventListener('change', () => { document.body.querySelector('#TWITCH_CHANNEL_NAME').addEventListener('change', () => {
settings.TWITCH.CHANNEL_NAME = document.body.querySelector('#TWITCH_CHANNEL_NAME').value; settings.TWITCH.CHANNEL_NAME = document.body.querySelector('#TWITCH_CHANNEL_NAME').value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
let button = document.body.querySelector('#TestTwitchCredentials'); const button = document.body.querySelector('#TestTwitchCredentials');
button.className = 'AdvancedMenuButton'; button.className = 'AdvancedMenuButton';
createNotification('Saved Channel name, please restart the application to reset twitch service', 'warning'); createNotification('Saved Channel name, please restart the application to reset twitch service', 'warning');
}); });
@ -138,11 +148,19 @@ document.body.querySelector('#TWITCH_OAUTH_TOKEN').addEventListener('change', ()
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved OAuth token!', 'success'); createNotification('Saved OAuth token!', 'success');
let button = document.body.querySelector('#TestTwitchCredentials'); const button = document.body.querySelector('#TestTwitchCredentials');
button.className = 'AdvancedMenuButton'; button.className = 'AdvancedMenuButton';
createNotification('Saved OAuth token, please restart the application to reset twitch service', 'warning'); createNotification('Saved OAuth token, please restart the application to reset twitch service', 'warning');
}); });
setInputFilter(
document.body.querySelector('#PORT'),
function (value) {
return /^\d*\.?\d*$/.test(value); // Allow digits and '.' only, using a RegExp.
},
"Only digits and '.' are allowed"
);
document.body.querySelector('#PORT').addEventListener('change', () => { document.body.querySelector('#PORT').addEventListener('change', () => {
settings.GENERAL.PORT = document.body.querySelector('#PORT').value; settings.GENERAL.PORT = document.body.querySelector('#PORT').value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -174,7 +192,7 @@ document.body.querySelector('#notification').addEventListener('change', () => {
}); });
function showMenuButton(menuButton, toggle) { function showMenuButton(menuButton, toggle) {
let option = document.body.querySelector(menuButton); const option = document.body.querySelector(menuButton);
if (!toggle) { if (!toggle) {
option.style.display = 'none'; option.style.display = 'none';
} else { } else {
@ -196,7 +214,7 @@ function createNotification(message = null, type = null) {
alertSound = 'error.mp3'; alertSound = 'error.mp3';
} }
let notfication = new Audio(path.join(resourcesPath, `./sounds/notifications/${alertSound}`)); const notfication = new Audio(path.join(resourcesPath, `./sounds/notifications/${alertSound}`));
notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100; notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100;
notfication.play(); notfication.play();
setTimeout(() => notification.remove(), 10000); setTimeout(() => notification.remove(), 10000);
@ -250,14 +268,14 @@ document.body.querySelector('#min-button').addEventListener('click', () => {
// #region Top bar buttons // #region Top bar buttons
document.body.querySelector('#Info_USERNAME').addEventListener('click', async () => { document.body.querySelector('#Info_USERNAME').addEventListener('click', async () => {
let element = document.body.querySelector('#TWITCH_OAUTH_TOKEN'); const element = document.body.querySelector('#TWITCH_OAUTH_TOKEN');
element.value = await auth.getTwitchOauthToken(); element.value = await auth.getTwitchOauthToken();
createNotification('Saved OAuth token!', 'success'); createNotification('Saved OAuth token!', 'success');
}); });
let hideInputToggleButton = document.body.querySelectorAll('.password-toggle-btn .password-toggle-icon .fa-eye-slash'); const hideInputToggleButton = document.body.querySelectorAll('.password-toggle-btn .password-toggle-icon .fa-eye-slash');
hideInputToggleButton.forEach((item) => { hideInputToggleButton.forEach(item => {
item.addEventListener('click', () => { item.addEventListener('click', () => {
if (item.classList.contains('fa-eye')) { if (item.classList.contains('fa-eye')) {
item.classList.remove('fa-eye'); item.classList.remove('fa-eye');
@ -391,7 +409,7 @@ document.body.querySelector('#USE_MODULES').addEventListener('click', () => {
createNotification( createNotification(
`${toggle ? 'Enabled' : 'Disabled'} server settings!, the service will stop working after restarting the application `${toggle ? 'Enabled' : 'Disabled'} server settings!, the service will stop working after restarting the application
${toggle ? '' : ', the service will stop working after restarting the application'}`, ${toggle ? '' : ', the service will stop working after restarting the application'}`,
'success', 'success'
); );
}); });
@ -403,7 +421,7 @@ document.body.querySelector('#USE_VTUBER').addEventListener('change', () => {
createNotification( createNotification(
`${toggle ? 'Enabled' : 'Disabled'} Vtuber setting! `${toggle ? 'Enabled' : 'Disabled'} Vtuber setting!
${toggle ? '' : ', the service will stop working after restarting the application'}`, ${toggle ? '' : ', the service will stop working after restarting the application'}`,
'success', 'success'
); );
server.startVtuberModule(); server.startVtuberModule();
}); });
@ -486,7 +504,7 @@ document.body.querySelector('#USE_NOTIFICATION_SOUNDS').addEventListener('change
}); });
document.body.querySelector('#notificationVolume').addEventListener('change', () => { document.body.querySelector('#notificationVolume').addEventListener('change', () => {
let element = document.body.querySelector('#notificationVolume'); const element = document.body.querySelector('#notificationVolume');
settings.AUDIO.NOTIFICATION_VOLUME = element.value; settings.AUDIO.NOTIFICATION_VOLUME = element.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -524,7 +542,7 @@ if (settings.AUDIO.NOTIFICATION_VOLUME) {
} }
document.body.querySelector('#ttsVolume').addEventListener('change', () => { document.body.querySelector('#ttsVolume').addEventListener('change', () => {
let element = document.body.querySelector('#ttsVolume'); const element = document.body.querySelector('#ttsVolume');
settings.AUDIO.TTS_VOLUME = element.value; settings.AUDIO.TTS_VOLUME = element.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -562,7 +580,7 @@ if (settings.AUDIO.TTS_VOLUME) {
} }
document.body.querySelector('#ttsVolume').addEventListener('change', () => { document.body.querySelector('#ttsVolume').addEventListener('change', () => {
let element = document.body.querySelector('#ttsVolume'); const element = document.body.querySelector('#ttsVolume');
settings.AUDIO.TTS_VOLUME = element.value; settings.AUDIO.TTS_VOLUME = element.value;
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
@ -575,10 +593,10 @@ document.body.querySelector('#TestDefaultTTSButton').addEventListener('click', a
const text = document.getElementById('testPrimaryTTS').value; const text = document.getElementById('testPrimaryTTS').value;
const requestData = { const requestData = {
message: `user: ${text}`, message: `user: ${text}`,
voice: settings.TTS.PRIMARY_VOICE, voice: settings.TTS.PRIMARY_VOICE
}; };
let count = await backend.getInternalTTSAudio(requestData); const count = await backend.getInternalTTSAudio(requestData);
let textObject = { filtered: text, formatted: text }; const textObject = { filtered: text, formatted: text };
sound.playAudio({ service: 'Internal', message: textObject, count }); sound.playAudio({ service: 'Internal', message: textObject, count });
}); });
@ -586,15 +604,89 @@ document.body.querySelector('#TestSecondaryTTSButton').addEventListener('click',
const text = document.getElementById('testSecondaryTTS').value; const text = document.getElementById('testSecondaryTTS').value;
const requestData = { const requestData = {
message: `user: ${text}`, message: `user: ${text}`,
voice: settings.TTS.SECONDARY_VOICE, voice: settings.TTS.SECONDARY_VOICE
}; };
let count = await backend.getInternalTTSAudio(requestData); const count = await backend.getInternalTTSAudio(requestData);
let textObject = { filtered: text, formatted: text }; const textObject = { filtered: text, formatted: text };
sound.playAudio({ service: 'Internal', message: textObject, count }); sound.playAudio({ service: 'Internal', message: textObject, count });
}); });
// Restricts input for the given textbox to the given inputFilter function.
function setInputFilter(textbox, inputFilter, errMsg) {
['input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', 'contextmenu', 'drop', 'focusout'].forEach(function (event) {
textbox.addEventListener(event, function (e) {
if (inputFilter(this.value)) {
// Accepted value.
if (['keydown', 'mousedown', 'focusout'].indexOf(e.type) >= 0) {
this.classList.remove('input-error');
this.setCustomValidity('');
}
this.oldValue = this.value;
this.oldSelectionStart = this.selectionStart;
this.oldSelectionEnd = this.selectionEnd;
} else if (Object.prototype.hasOwnProperty.call(this, 'oldValue')) {
// Rejected value: restore the previous one.
this.classList.add('input-error');
this.setCustomValidity(errMsg);
this.reportValidity();
this.value = this.oldValue;
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
} else {
// Rejected value: nothing to restore.
this.value = '';
}
});
});
}
webFrame.setVisualZoomLevelLimits(1, 5);
document.body.addEventListener('wheel', e => {
if (e.ctrlKey) {
const currentZoom = webFrame.getZoomFactor();
const zoomIn = Boolean(e.deltaY < 0);
setZoomLevel(currentZoom, zoomIn);
}
});
setInputFilter(
document.body.querySelector('#ZOOMLEVEL'),
function (value) {
return /^\d*\.?\d*$/.test(value); // Allow digits and '.' only, using a RegExp.
},
"Only digits and '.' are allowed"
);
document.body.querySelector('#ZOOMLEVEL').addEventListener('change', () => {
const newZoom = parseInt(document.body.querySelector('#ZOOMLEVEL').value) / 100;
settings.GENERAL.ZOOMLEVEL = newZoom;
fs.writeFileSync(settingsPath, ini.stringify(settings));
setZoomLevel(newZoom, null);
createNotification('Saved zoom new level', 'warning');
});
document.body.querySelector('emoji-picker').addEventListener('emoji-click', e => {
console.log(e.detail);
const div = document.getElementById('textInput');
if (e.detail.unicode === undefined) {
div.value += e.detail.name + ' ';
} else {
div.value += e.detail.unicode + ' ';
}
div.focus();
});
// #region Use Custom theme toggle logic
document.body.querySelector('#emojis').addEventListener('click', () => {
const emojiPicker = document.body.querySelector('#emoji-picker');
// console.log(emojiPicker);
emojiPicker.style.visibility === 'visible' ? (emojiPicker.style.visibility = 'hidden') : (emojiPicker.style.visibility = 'visible');
});
module.exports = { module.exports = {
getGeneralSettings, getGeneralSettings
}; };

View file

@ -1,23 +1,24 @@
/* global ttsAudioFile, path, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */
let trueMessage = ''; let trueMessage = '';
let currentLogoUrl = ''; let currentLogoUrl = '';
let currentUsername = ''; let currentUsername = '';
let voiceSoundArray = []; const voiceSoundArray = [];
let status = 0; let status = 0;
let counter = 0; const counter = 0;
const playTTS = (data) => const playTTS = data =>
new Promise((resolve) => { new Promise(resolve => {
ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`); ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`);
const tts = new Audio(ttsAudioFile); const tts = new Audio(ttsAudioFile);
console.log(settings.AUDIO.TTS_AUDIO_DEVICE); // console.log(settings.AUDIO.TTS_AUDIO_DEVICE);
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE); tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
tts.addEventListener('ended', () => { tts.addEventListener('ended', () => {
console.log('ended'); // console.log('ended');
fs.unlink(ttsAudioFile, (err) => { fs.unlink(ttsAudioFile, err => {
if (err) { if (err) {
console.error('TEST'); console.error(err);
resolve('finished'); resolve('finished');
return; return;
} }
@ -25,15 +26,19 @@ const playTTS = (data) =>
}); });
}); });
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE) tts
.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
.then(() => { .then(() => {
console.log('playing'); // console.log('playing');
tts.volume = settings.AUDIO.TTS_VOLUME / 100; tts.volume = settings.AUDIO.TTS_VOLUME / 100;
tts.play().catch((error) => { tts.play().catch(error => {
if (error) {
console.error(error);
}
resolve('finished'); resolve('finished');
}); });
}) })
.catch((error) => { .catch(error => {
console.error('Failed to set audio output device:', error); console.error('Failed to set audio output device:', error);
resolve('finished'); resolve('finished');
}); });
@ -56,11 +61,24 @@ function add(data) {
function playNotificationSound() { function playNotificationSound() {
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
let notfication = new Audio( const notfication = new Audio(
path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`), path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`)
); );
notfication
.setSinkId(settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE)
.then(() => {
// console.log('playing');
notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100; notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100;
notfication.play(); notfication.play().catch(error => {
if (error) {
console.error(error);
}
});
})
.catch(error => {
console.error('Failed to set audio output device:', error);
});
} }
} }
@ -75,7 +93,7 @@ async function playVoice(filteredMessage, logoUrl, username, message) {
trueMessage = filteredMessage; trueMessage = filteredMessage;
currentLogoUrl = logoUrl; currentLogoUrl = logoUrl;
currentUsername = username; currentUsername = username;
let textObject = { filtered: filteredMessage, formatted: message }; const textObject = { filtered: filteredMessage, formatted: message };
let voice; let voice;
textObject.filtered = `${username}: ${filteredMessage}`; textObject.filtered = `${username}: ${filteredMessage}`;
@ -93,15 +111,16 @@ async function playVoice(filteredMessage, logoUrl, username, message) {
const service = document.getElementById('primaryTTSService').value; const service = document.getElementById('primaryTTSService').value;
switch (service) { switch (service) {
case 'Internal': case 'Internal': {
const requestData = { const requestData = {
message: textObject.filtered, message: textObject.filtered,
voice: settings.TTS.PRIMARY_VOICE, voice: settings.TTS.PRIMARY_VOICE
}; };
let count = await backend.getInternalTTSAudio(requestData); const count = await backend.getInternalTTSAudio(requestData);
playAudio({ service, message: textObject, count }); playAudio({ service, message: textObject, count });
break; break;
}
case 'Amazon': case 'Amazon':
// playAudio({ service: 'Amazon', message: textObject, count }); // playAudio({ service: 'Amazon', message: textObject, count });
break; break;

View file

@ -1,3 +1,5 @@
/* global settings, root, fs, settingsPath, ini */
function changeColor(section, setting, tempSection) { function changeColor(section, setting, tempSection) {
document.querySelector(section).value = setting; document.querySelector(section).value = setting;
const value = document.querySelector(section).value; const value = document.querySelector(section).value;
@ -12,15 +14,11 @@ function setCurrentTheme(adjustTemp = false) {
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar'); changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar');
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section'); changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section');
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble'); changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble');
changeColor( changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header');
'#CHAT_BUBBLE_HEADER',
settings.THEME.CHAT_BUBBLE_HEADER,
adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header',
);
changeColor( changeColor(
'#CHAT_BUBBLE_MESSAGE', '#CHAT_BUBBLE_MESSAGE',
settings.THEME.CHAT_BUBBLE_MESSAGE, settings.THEME.CHAT_BUBBLE_MESSAGE,
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message', adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message'
); );
} }

View file

@ -1,7 +1,10 @@
/* global client, customEmojis, emojiPicker, settings, options, sound, showChatMessage, messageTemplates, getPostTime */
const tmi = require('tmi.js'); const tmi = require('tmi.js');
const axios = require('axios'); const axios = require('axios');
let client; let client = null;
let logoUrl = null;
function sendMessage(message) { function sendMessage(message) {
client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error); client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error);
@ -9,30 +12,30 @@ function sendMessage(message) {
client = new tmi.Client({ client = new tmi.Client({
options: { options: {
skipUpdatingEmotesets: true, skipUpdatingEmotesets: true
}, },
identity: { identity: {
username: settings.TWITCH.USERNAME, username: settings.TWITCH.USERNAME,
password: settings.TWITCH.OAUTH_TOKEN, password: settings.TWITCH.OAUTH_TOKEN
}, },
channels: [settings.TWITCH.CHANNEL_NAME], channels: [settings.TWITCH.CHANNEL_NAME]
}); });
client client
.connect() .connect()
.then((data) => {}) .then(data => {})
.catch(console.error); .catch(console.error);
function ping(element) { function ping(element) {
let value = document.body.querySelector(element); const value = document.body.querySelector(element);
client client
.ping() .ping()
.then((data) => { .then(data => {
value.classList.add('success'); value.classList.add('success');
value.innerText = 'Success!'; value.innerText = 'Success!';
}) })
.catch((e) => { .catch(e => {
value.classList.add('error'); value.classList.add('error');
value.innerText = 'Failed!'; value.innerText = 'Failed!';
}); });
@ -64,7 +67,7 @@ function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage
const msg = article.querySelector('.msg-box'); const msg = article.querySelector('.msg-box');
if (msg) { if (msg) {
messageObject.forEach((entry) => { messageObject.forEach(entry => {
if (entry.text) { if (entry.text) {
msg.innerHTML += entry.text; msg.innerHTML += entry.text;
} else { } else {
@ -74,7 +77,7 @@ function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage
} }
// Appends the message to the main chat box (shows the message) // Appends the message to the main chat box (shows the message)
showChatMessage(article, false); showChatMessage(article);
if (fileteredMessage) { if (fileteredMessage) {
sound.playVoice(fileteredMessage, logoUrl, username, msg); sound.playVoice(fileteredMessage, logoUrl, username, msg);
@ -88,16 +91,16 @@ function getProfileImage(userid, username, message, fileteredMessage) {
options = { options = {
method: 'GET', method: 'GET',
url: `https://api.twitch.tv/helix/users?id=${userid}`, url: `https://api.twitch.tv/helix/users?id=${userid}`,
headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }, headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }
}; };
axios axios
.request(options) .request(options)
.then((responseLogoUrl) => { .then(responseLogoUrl => {
const logoUrl = responseLogoUrl.data.data[0].profile_image_url; logoUrl = responseLogoUrl.data.data[0].profile_image_url;
displayTwitchMessage(logoUrl, username, message, fileteredMessage); displayTwitchMessage(logoUrl, username, message, fileteredMessage);
}) })
.catch((error) => { .catch(error => {
console.error(error); console.error(error);
}); });
} }
@ -125,20 +128,59 @@ client.on('message', (channel, tags, message, self) => {
} }
const emotes = tags.emotes || {}; const emotes = tags.emotes || {};
const emoteValues = Object.entries(emotes); const emoteValues = Object.entries(emotes);
let fileteredMessage = message; let filteredMessage = message;
let emoteMessage = message; let emoteMessage = message;
emoteValues.forEach((entry) => { emoteValues.forEach(entry => {
entry[1].forEach((lol) => { entry[1].forEach(lol => {
const [start, end] = lol.split('-'); const [start, end] = lol.split('-');
let emote = `<img src="https://static-cdn.jtvnw.net/emoticons/v2/${entry[0]}/default/dark/1.0"/>`; const emote = `<img src="https://static-cdn.jtvnw.net/emoticons/v2/${entry[0]}/default/dark/1.0"/>`;
emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote); emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote);
fileteredMessage = fileteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), ''); filteredMessage = filteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
}); });
}); });
let messageObject = parseString(emoteMessage); const messageObject = parseString(emoteMessage);
getProfileImage(tags['user-id'], tags['display-name'], messageObject, fileteredMessage); getProfileImage(tags['user-id'], tags['display-name'], messageObject, filteredMessage);
}); });
function formatTwitchEmojis(emojis, name) {
emojis.forEach(emoji => {
const emojiToBeAdded = {
name: emoji.name,
shortcodes: [emoji.name],
url: emoji.images.url_1x,
category: name
};
customEmojis.push(emojiToBeAdded);
});
emojiPicker.customEmoji = customEmojis;
}
function getTwitchGLobalEmotes() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://api.twitch.tv/helix/chat/emotes/global',
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
formatTwitchEmojis(responseLogoUrl.data.data, 'Twitch Global');
// console.log(responseLogoUrl);
})
.catch(error => {
console.error(error);
});
}
if (settings.TWITCH.OAUTH_TOKEN) {
getTwitchGLobalEmotes();
}
module.exports = { sendMessage, ping, client }; module.exports = { sendMessage, ping, client };

View file

@ -1,3 +1,5 @@
/* global pythonPath, a */
const { app, BrowserWindow, ipcMain } = require('electron'); const { app, BrowserWindow, ipcMain } = require('electron');
const { writeIniFile } = require('write-ini-file'); const { writeIniFile } = require('write-ini-file');
const path = require('path'); const path = require('path');
@ -45,8 +47,8 @@ async function createWindow() {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
enableRemoteModule: true, enableRemoteModule: true
}, }
}); });
window.loadFile(path.join(__dirname, 'index.html')); window.loadFile(path.join(__dirname, 'index.html'));
@ -55,7 +57,7 @@ async function createWindow() {
window.webContents.openDevTools(); window.webContents.openDevTools();
} }
window.on('close', (e) => { window.on('close', e => {
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program
const bounds = window.getBounds(); const bounds = window.getBounds();
@ -72,7 +74,7 @@ app.whenReady().then(() => {
createWindow(); createWindow();
}); });
app.on('window-all-closed', (event) => { app.on('window-all-closed', event => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit(); app.quit();
} }
@ -93,12 +95,12 @@ ipcMain.on('resize-window', (event, width, height) => {
browserWindow.setSize(width, height); browserWindow.setSize(width, height);
}); });
ipcMain.on('minimize-window', (event) => { ipcMain.on('minimize-window', event => {
const browserWindow = BrowserWindow.fromWebContents(event.sender); const browserWindow = BrowserWindow.fromWebContents(event.sender);
browserWindow.minimize(); browserWindow.minimize();
}); });
ipcMain.on('maximize-window', (event) => { ipcMain.on('maximize-window', event => {
const browserWindow = BrowserWindow.fromWebContents(event.sender); const browserWindow = BrowserWindow.fromWebContents(event.sender);
if (!browserWindow.isMaximized()) { if (!browserWindow.isMaximized()) {
@ -108,18 +110,18 @@ ipcMain.on('maximize-window', (event) => {
} }
}); });
ipcMain.on('close-window', (event) => { ipcMain.on('close-window', event => {
const browserWindow = BrowserWindow.fromWebContents(event.sender); const browserWindow = BrowserWindow.fromWebContents(event.sender);
kill('loquendoBot_backend'); kill('loquendoBot_backend');
browserWindow.close(); browserWindow.close();
app.quit(); app.quit();
}); });
ipcMain.on('restart', (event) => { ipcMain.on('restart', event => {
app.relaunch(); app.relaunch();
}); });
ipcMain.on('environment', (event) => { ipcMain.on('environment', event => {
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged }; event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
}); });
@ -136,32 +138,34 @@ async function createIniFile() {
PORT: 9000, PORT: 9000,
VIEWERS_PANEL: false, VIEWERS_PANEL: false,
LOCATION: pythonPath, LOCATION: pythonPath,
ZOOMLEVEL: 1
}, },
LANGUAGE: { LANGUAGE: {
USE_DETECTION: false, USE_DETECTION: false
}, },
TTS: { TTS: {
USE_TTS: true, USE_TTS: true,
PRIMARY_VOICE: '', PRIMARY_VOICE: '',
PRIMARY_TTS_LANGUAGE: 'EN', PRIMARY_TTS_LANGUAGE: 'EN',
SECONDARY_VOICE: '', SECONDARY_VOICE: '',
SECONDARY_TTS_LANGUAGE: 'EN', SECONDARY_TTS_LANGUAGE: 'EN'
}, },
STT: { STT: {
USE_STT: false, USE_STT: false,
MICROPHONE_ID: 'default', MICROPHONE_ID: 'default',
SELECTED_MICROPHONE: 'default', SELECTED_MICROPHONE: 'default',
MICROPHONE: 0, MICROPHONE: 0,
LANGUAGE: 'vosk-model-small-es-0.42', LANGUAGE: 'vosk-model-small-es-0.42'
}, },
AUDIO: { AUDIO: {
USE_NOTIFICATION_SOUNDS: true, USE_NOTIFICATION_SOUNDS: true,
SELECTED_NOTIFICATION_AUDIO_DEVICE: 'default',
NOTIFICATION_AUDIO_DEVICE: 0, NOTIFICATION_AUDIO_DEVICE: 0,
NOTIFICATION_SOUND: 0, NOTIFICATION_SOUND: 0,
NOTIFICATION_VOLUME: 50, NOTIFICATION_VOLUME: 50,
SELECTED_TTS_AUDIO_DEVICE: 0, SELECTED_TTS_AUDIO_DEVICE: 0,
TTS_AUDIO_DEVICE: 'default', TTS_AUDIO_DEVICE: 'default',
TTS_VOLUME: 50, TTS_VOLUME: 50
}, },
THEME: { THEME: {
USE_CUSTOM_THEME: false, USE_CUSTOM_THEME: false,
@ -173,7 +177,7 @@ async function createIniFile() {
MID_SECTION: '#6b8578', MID_SECTION: '#6b8578',
CHAT_BUBBLE_BG: '#447466', CHAT_BUBBLE_BG: '#447466',
CHAT_BUBBLE_HEADER: '#ffffff', CHAT_BUBBLE_HEADER: '#ffffff',
CHAT_BUBBLE_MESSAGE: '#b5b5b5', CHAT_BUBBLE_MESSAGE: '#b5b5b5'
}, },
TWITCH: { TWITCH: {
USE_TWITCH: false, USE_TWITCH: false,
@ -182,12 +186,12 @@ async function createIniFile() {
USER_ID: '', USER_ID: '',
USER_LOGO_URL: '', USER_LOGO_URL: '',
OAUTH_TOKEN: '', OAUTH_TOKEN: '',
CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9', CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9'
}, },
MODULES: { MODULES: {
USE_MODULES: false, USE_MODULES: false,
USE_VTUBER: false, USE_VTUBER: false,
USE_CHATBUBBLE: false, USE_CHATBUBBLE: false
}, },
AMAZON: { AMAZON: {
USE_AMAZON: false, USE_AMAZON: false,
@ -195,15 +199,15 @@ async function createIniFile() {
ACCESS_SECRET: '', ACCESS_SECRET: '',
PRIMARY_VOICE: '', PRIMARY_VOICE: '',
SECONDARY_VOICE: '', SECONDARY_VOICE: '',
CHARACTERS_USED: 0, CHARACTERS_USED: 0
}, },
GOOGLE: { GOOGLE: {
USE_GOOGLE: false, USE_GOOGLE: false,
API_KEY: '', API_KEY: '',
PRIMARY_VOICE: '', PRIMARY_VOICE: '',
SECONDARY_VOICE: '', SECONDARY_VOICE: '',
CHARACTERS_USED: 0, CHARACTERS_USED: 0
}, }
}).then(() => { }).then(() => {
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8'));
}); });

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<title>Chat</title> <title>Chat</title>

View file

@ -11,7 +11,7 @@ body {
.thomas { .thomas {
position: relative; position: relative;
float: center; float: center;
display: inline-block; /* display: inline-block; */
} }
.speechbubble { .speechbubble {
@ -148,9 +148,9 @@ body {
padding: 15px; padding: 15px;
border-radius: 20px; border-radius: 20px;
} }
/*
.message::after { .message::after {
} } */
.arrow { .arrow {
content: ''; content: '';

View file

@ -1,3 +1,5 @@
/* global io */
// Connect to the Socket.IO server // Connect to the Socket.IO server
const socket = io(); const socket = io();
@ -28,16 +30,16 @@ let x;
let currentIndex = 0; let currentIndex = 0;
let messageStream = ''; let messageStream = '';
let tempMessageObject = ''; const tempMessageObject = '';
let fullMessageLength = 0; const fullMessageLength = 0;
function getFullMessageLength(text) { function getFullMessageLength(text) {
let fullMessageLength = 0; let fullMessageLength = 0;
text.forEach((element) => { text.forEach(element => {
if (element.text) { if (element.text) {
fullMessageLength += element.text.length; fullMessageLength += element.text.length;
} }
element.html; // element.html;
fullMessageLength += 1; fullMessageLength += 1;
}); });
@ -97,16 +99,16 @@ function displayTwitchMessage(logoUrl, username, messageObject) {
elements[0].remove(); elements[0].remove();
} }
article.addEventListener('animationend', (e) => { article.addEventListener('animationend', e => {
if (e.animationName == 'fade-outx') { if (e.animationName === 'fade-outx') {
article.remove(); article.remove();
} }
}); });
if (elements.length > 1) { if (elements.length > 1) {
elements[0].classList.add('fade-outxx'); elements[0].classList.add('fade-outxx');
elements[0].addEventListener('animationend', (e) => { elements[0].addEventListener('animationend', e => {
if (e.animationName == 'fade-outxx') { if (e.animationName === 'fade-outxx') {
elements[0].remove(); elements[0].remove();
} }
}); });

View file

@ -20,8 +20,8 @@ body {
padding-left: 40dip; padding-left: 40dip;
padding-right: 10dip; padding-right: 10dip;
vertical-align: top; vertical-align: top;
foreground-repeat: no-repeat; /* foreground-repeat: no-repeat;
foreground-position: 16dip 50%; foreground-position: 16dip 50%; */
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 10dip 50%; background-position: 10dip 50%;
background-size: 64dip 64dip; background-size: 64dip 64dip;
@ -37,7 +37,7 @@ body {
.title { .title {
font-weight: bold !important; font-weight: bold !important;
flow: column !important; /* flow: column !important; */
text-align: left !important; text-align: left !important;
margin: auto !important; margin: auto !important;
width: min-content !important; width: min-content !important;
@ -86,12 +86,12 @@ img {
} }
#button-bar { #button-bar {
flow: horizontal; /* flow: horizontal; */
padding: 10dip; padding: 10dip;
border-spacing: 10dip; border-spacing: 10dip;
margin: 0; margin: 0;
flow: horizontal; /* flow: horizontal; */
horizontal-align: right; /* horizontal-align: right; */
} }
label { label {
@ -103,7 +103,7 @@ label {
text-shadow: #fff 0px 1px; text-shadow: #fff 0px 1px;
min-width: 4em; min-width: 4em;
line-height: 2em; line-height: 2em;
vertical-align: middle; /* vertical-align: middle; */
width: min-intrinsic; width: min-intrinsic;
text-align: center; text-align: center;
} }

View file

@ -248,7 +248,6 @@ button.motion {
width: 50px; width: 50px;
} }
.closed-mouth-motion { .closed-mouth-motion {
background-image: url('../png/controls/buttons/top/motion/closed.png'); background-image: url('../png/controls/buttons/top/motion/closed.png');
box-sizing: border-box; box-sizing: border-box;
@ -311,7 +310,6 @@ button.motion {
background-color: red; background-color: red;
} }
#mouth-transition::before { #mouth-transition::before {
background-color: red; background-color: red;
background-image: url('../png/controls/buttons/top/avatar-change/border/default.png'); background-image: url('../png/controls/buttons/top/avatar-change/border/default.png');
@ -380,7 +378,3 @@ button.motion::after {
width: 40%; width: 40%;
height: width(100%); height: width(100%);
} }
menu.popup {
/* box-shadow: 3px 3px 1px rgba(0, 0, 0, 0.692); */
}

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html window-frame="transparent"> <html window-frame="transparent">
<head> <head>
<title>TransTube</title> <title>TransTube</title>

View file

@ -41,9 +41,7 @@ monitorDelay();
function animate() { function animate() {
let avatarCurr = $('#avatar').src; let avatarCurr = $('#avatar').src;
const animation = globalThis.MOUTH_IS_OPEN const animation = globalThis.MOUTH_IS_OPEN ? globalThis.CHOSEN_OPEN_MOUTH_ANIMATION : globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION;
? globalThis.CHOSEN_OPEN_MOUTH_ANIMATION
: globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION;
globalThis.ANIMATION = animation; globalThis.ANIMATION = animation;
@ -137,10 +135,7 @@ function animateButton(button, animation = 'motionless') {
}); });
} }
animateButton( animateButton('#closed-mouth-motion', globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION);
'#closed-mouth-motion',
globalThis.CHOSEN_CLOSED_MOUTH_ANIMATION,
);
animateButton('#open-mouth-motion', globalThis.CHOSEN_OPEN_MOUTH_ANIMATION); animateButton('#open-mouth-motion', globalThis.CHOSEN_OPEN_MOUTH_ANIMATION);
function blink() { function blink() {
@ -177,21 +172,13 @@ async function _cycleAnimations() {
animate(key); animate(key);
Window.this.caption = key; Window.this.caption = key;
i++; i++;
await new Promise((r) => setTimeout(r, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
} }
} }
$(document).on( $(document).on('~mousedown', '#closed-mouth-motion, #open-mouth-motion', motionButtonEvent);
'~mousedown',
'#closed-mouth-motion, #open-mouth-motion',
motionButtonEvent,
);
$(document).on( $(document).on('~doubleclick', '#closed-mouth-motion, #open-mouth-motion', motionButtonEvent);
'~doubleclick',
'#closed-mouth-motion, #open-mouth-motion',
motionButtonEvent,
);
function motionButtonEvent(evt) { function motionButtonEvent(evt) {
const { target: button } = evt; const { target: button } = evt;
@ -203,26 +190,10 @@ function motionButtonEvent(evt) {
button.attributes.counter--; button.attributes.counter--;
} }
const color = [ const color = ['white', '#9BCCD4', '#8087D6', '#AB65CF', '#E7FD5B', '#EC9F45', '#E24555'][mod(button.attributes.counter, 7)];
'white',
'#9BCCD4',
'#8087D6',
'#AB65CF',
'#E7FD5B',
'#EC9F45',
'#E24555',
][mod(button.attributes.counter, 7)];
button.style.variable('color', color); button.style.variable('color', color);
const animation = [ const animation = ['motionless', 'vibing', 'shaking', 'shakingMore', 'bouncy', 'excited', 'nervous'][mod(button.attributes.counter, 7)];
'motionless',
'vibing',
'shaking',
'shakingMore',
'bouncy',
'excited',
'nervous',
][mod(button.attributes.counter, 7)];
animateButton(button, animation); animateButton(button, animation);
@ -238,7 +209,8 @@ function motionButtonEvent(evt) {
function animateMouthButton() { function animateMouthButton() {
setInterval(() => { setInterval(() => {
const n = Date.now() % 1200; const n = Date.now() % 1200;
document.body.querySelector('.mouth-transition').style.backgroundImage = `url('../vtuber/png/controls/buttons/top/motion/${n > 600 ? 'open' : 'closed' document.body.querySelector('.mouth-transition').style.backgroundImage = `url('../vtuber/png/controls/buttons/top/motion/${
n > 600 ? 'open' : 'closed'
}.png')`; }.png')`;
}); });
} }
@ -251,21 +223,16 @@ function mod(n, m) {
globalThis.CURRENT_BUTTON = null; globalThis.CURRENT_BUTTON = null;
$(document).on( $(document).on('click', '.mouth-image.border-default:not(:first-of-type)', (evt, el) => {
'click',
'.mouth-image.border-default:not(:first-of-type)',
(evt, el) => {
globalThis.CURRENT_BUTTON = el; globalThis.CURRENT_BUTTON = el;
el.popup($('menu')); el.popup($('menu'));
}, });
);
function loadImage() { function loadImage() {
const filename = Window.this.selectFile({ const filename = Window.this.selectFile({
mode: 'open', mode: 'open',
filter: filter: 'image files (*.bmp,*.dib,*.gif,*.png,*.apng,*.jpg,*.jpeg,*.jiff)|*.bmp;*.dib;*.gif;*.png;*.apng;*.jpg;*.jpeg;*.jiff',
'image files (*.bmp,*.dib,*.gif,*.png,*.apng,*.jpg,*.jpeg,*.jiff)|*.bmp;*.dib;*.gif;*.png;*.apng;*.jpg;*.jpeg;*.jiff', caption: 'select image for closed mouth...'
caption: 'select image for closed mouth...',
}); });
return filename; return filename;
} }
@ -279,9 +246,7 @@ function changeImage(evt, el) {
const filename = loadImage(); const filename = loadImage();
if (!filename) return; if (!filename) return;
const which = globalThis.CURRENT_BUTTON.id const which = globalThis.CURRENT_BUTTON.id.match(/closed|open|blink/g).join('-');
.match(/closed|open|blink/g)
.join('-');
document.style.variable(which, `url('${filename}')`); document.style.variable(which, `url('${filename}')`);
globalThis.CURRENT_BUTTON.classList.add('border-default'); globalThis.CURRENT_BUTTON.classList.add('border-default');
@ -289,11 +254,7 @@ function changeImage(evt, el) {
}); });
} }
$(document).on( $(document).on('click', '.mouth-image:first-of-type, .mouth-image.border-add', changeImage);
'click',
'.mouth-image:first-of-type, .mouth-image.border-add',
changeImage,
);
$(document).on('click', '#change-image', changeImage); $(document).on('click', '#change-image', changeImage);
@ -301,9 +262,7 @@ $(document).on('click', '#remove-image', (evt, el) => {
globalThis.CURRENT_BUTTON.classList.remove('border-default'); globalThis.CURRENT_BUTTON.classList.remove('border-default');
globalThis.CURRENT_BUTTON.classList.add('border-add'); globalThis.CURRENT_BUTTON.classList.add('border-add');
const which = globalThis.CURRENT_BUTTON.id const which = globalThis.CURRENT_BUTTON.id.match(/closed|open|blink/g).join('-');
.match(/closed|open|blink/g)
.join('-');
document.style.variable(which, null); document.style.variable(which, null);
}); });

View file

@ -7,7 +7,7 @@ export default {
shakingMore, shakingMore,
bouncy, bouncy,
excited, excited,
nervous, nervous
}; };
function motionless(t) { function motionless(t) {
@ -25,7 +25,7 @@ function _shake(t, amount, velocity) {
num2 = num2 * 2 - 1; num2 = num2 * 2 - 1;
return { return {
x: amount * (num * Math.sqrt(1 - (num2 * num2) / 2)), x: amount * (num * Math.sqrt(1 - (num2 * num2) / 2)),
y: amount * (num2 * Math.sqrt(1 - (num * num) / 2)), y: amount * (num2 * Math.sqrt(1 - (num * num) / 2))
}; };
} }

View file

@ -1,4 +1,5 @@
if (!Number.prototype.limit) { if (!Number.prototype.limit) {
// eslint-disable-next-line no-extend-native
Number.prototype.limit = function (min, max) { Number.prototype.limit = function (min, max) {
if (this < min) return min; if (this < min) return min;
if (this > max) return max; if (this > max) return max;
@ -7,8 +8,11 @@ if (!Number.prototype.limit) {
} }
function movableView(s, screenBound = false) { function movableView(s, screenBound = false) {
let xoff; let yoff; let minXY; let maxX; let let xoff;
maxY; let yoff;
let minXY;
let maxX;
let maxY;
let dragging = false; let dragging = false;
function screenBounds() { function screenBounds() {
@ -38,10 +42,7 @@ function movableView(s, screenBound = false) {
function onMouseMove(e) { function onMouseMove(e) {
if (dragging) { if (dragging) {
Window.this.move( Window.this.move((e.screenX - xoff).limit(minXY, maxX), (e.screenY - yoff).limit(minXY, maxY));
(e.screenX - xoff).limit(minXY, maxX),
(e.screenY - yoff).limit(minXY, maxY),
);
} }
} }

File diff suppressed because it is too large Load diff