Compare commits

..

No commits in common. "main" and "2.4.0" have entirely different histories.
main ... 2.4.0

353 changed files with 5604 additions and 51368 deletions

View file

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

View file

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

View file

@ -1,16 +0,0 @@
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'
}
};

8
.gitignore vendored
View file

@ -104,10 +104,4 @@ src/sounds/tts/*
loquendoBot_backend.spec loquendoBot_backend.spec
forge.config.js forge.config.js
backend/* backend/*
!backend/loquendoBot_backend.py src/backend/loquendoBot_backend.exe
backend/loquendoBot_backend.exe
src/config/twitch-emotes.json
dist/*
src/config/betterttv-emotes.json
test.py
src/config/settings.json

View file

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

36
forge.config.js Normal file
View file

@ -0,0 +1,36 @@
module.exports = {
packagerConfig: {
icon: './src/images/icon.ico',
asar: true,
extraResource: ['./src/config/loquendo.db', './src/sounds', './backend', './language_detection_model', './speech_to_text_models'],
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
setupIcon: './src/images/icon.ico',
},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {
options: {},
},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
],
};

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Khyretos Copyright (c) 2021 Khyretis
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,78 +1,43 @@
{ {
"name": "loquendo-bot", "name": "loquendo-bot",
"productName": "LoquendoBot", "version": "2.4.0",
"version": "2.6.0", "description": "Bot assistant for streamers over different platforms",
"description": "Bot assistant for streamers over different platforms", "main": "src/main.js",
"main": "src/main.js", "scripts": {
"scripts": { "start": "electron-forge start",
"start": "electron-forge start", "package": "npm run backend && electron-forge package",
"build": "npm run backend && electron-builder", "make": "electron-forge make",
"publish": "electron-forge publish", "publish": "electron-forge publish",
"backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./backend/loquendoBot_backend.py" "backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./src/backend/loquendoBot_backend.py"
},
"build": {
"appId": "LoquendoBot",
"win": {
"target": [
"nsis"
],
"icon": "./src/images/icon.ico"
}, },
"nsis": { "keywords": [],
"oneClick": false, "author": {
"installerIcon": "./src/images/icon.ico", "name": "Khyretos",
"uninstallerIcon": "./src/images/icon.ico", "email": "khyretos@gmail.com"
"uninstallDisplayName": "LoquendoBot-uninstaller",
"license": "license.md",
"allowToChangeInstallationDirectory": "true"
}, },
"extraResources": [ "license": "ISC",
"speech_to_text_models/Where to get STT models.txt", "dependencies": {
"backend/loquendoBot_backend.exe", "axios": "^1.4.0",
"language_detection_model", "electron-squirrel-startup": "^1.0.0",
"sounds" "express": "^4.18.2",
] "ini": "^2.0.0",
}, "kill-process-by-name": "^1.0.5",
"keywords": [], "node-google-tts-api": "^1.1.1",
"author": { "querystring": "^0.2.1",
"name": "Khyretos", "socket.io": "^4.7.1",
"email": "khyretos@gmail.com" "socket.io-client": "^4.7.1",
}, "tmi.js": "^1.8.5",
"license": "ISC", "url": "^0.11.1",
"dependencies": { "winston": "^3.10.0",
"@mediapipe/tasks-vision": "^0.10.12", "write-ini-file": "^4.0.1"
"axios": "^1.4.0", },
"dlivetv-api": "^1.0.10", "devDependencies": {
"emoji-picker-element": "^1.21.0", "@electron-forge/cli": "^6.2.1",
"express": "^4.18.2", "@electron-forge/maker-deb": "^6.2.1",
"flag-icons": "^7.1.0", "@electron-forge/maker-rpm": "^6.2.1",
"ini": "^2.0.0", "@electron-forge/maker-squirrel": "^6.2.1",
"kill-process-by-name": "^1.0.5", "@electron-forge/maker-zip": "^6.2.1",
"node-google-tts-api": "^1.1.1", "@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
"p5": "^1.9.2", "electron": "^25.9.8"
"querystring": "^0.2.1", }
"socket.io": "^4.7.1",
"socket.io-client": "^4.7.1",
"sockette": "^2.0.6",
"tmi.js": "^1.8.5",
"url": "^0.11.1",
"winston": "^3.10.0",
"write-ini-file": "^4.0.1",
"youtube-chat": "^2.2.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.2.1",
"@electron-forge/maker-deb": "^6.2.1",
"@electron-forge/maker-rpm": "^6.2.1",
"@electron-forge/maker-zip": "^6.2.1",
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
"@electron-internal/eslint-config": "^1.0.1",
"@electron-toolkit/eslint-config": "^1.0.2",
"electron": "^25.9.8",
"electron-builder": "^24.9.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.2",
"prettier": "^3.1.1"
}
} }

View file

@ -45,6 +45,5 @@ After getting your credentials you can set it in <img src="https://raw.githubuse
* [Speech2Go](https://harposoftware.com/en/spanish-spain-/340-S2G-Jorge-Nuance-Voice.html). * [Speech2Go](https://harposoftware.com/en/spanish-spain-/340-S2G-Jorge-Nuance-Voice.html).
### Linux ### Linux
* WIP * WIP
### Mac ### Mac
* WIP * WIP

View file

@ -1,3 +1 @@
Download the model from here: https://alphacephei.com/vosk/models unzip it https://alphacephei.com/vosk/models
and drop the folder in the 'speech_to_text_models' folder. Restart the app
to load the changes.

View file

@ -1,17 +1,10 @@
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
import configparser import configparser
import pyttsx3 import pyttsx3
@ -28,29 +21,25 @@ from deep_translator import (
MyMemoryTranslator, MyMemoryTranslator,
) )
import emoji
from vosk import Model, KaldiRecognizer, SetLogLevel from vosk import Model, KaldiRecognizer, SetLogLevel
# global variables # global variables
SetLogLevel(-1) SetLogLevel(-1)
settings = None; settings = configparser.ConfigParser()
app = Flask(__name__) app = Flask(__name__)
if len(sys.argv) > 1: if len(sys.argv) > 1:
settingsPath = os.path.normpath(sys.argv[1]) settingsPath = os.path.normpath(sys.argv[1])
environment = sys.argv[2] environment = sys.argv[2]
q = queue.Queue() q = queue.Queue()
# gobal functions # gobal functions
def loadSettings():
with open(settingsPath, 'r') as file:
global settings
settings = json.load(file)
# classes # classes
class LanguageDetection: class LanguageDetection:
def __init__(self): def __init__(self):
@ -67,18 +56,19 @@ class LanguageDetection:
resources_folder, "language_detection_model", f"lid.176.bin" resources_folder, "language_detection_model", f"lid.176.bin"
) )
language_detection_model = rf"{language_detection_model}" 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):
predictions = self.model.predict(text, k=3) # returns top 2 matching languages predictions = self.model.predict(text, k=5) # returns top 2 matching languages
language_codes = [] language_codes = []
for prediction in predictions[0]: for prediction in predictions[0]:
language_codes.append(prediction.replace("__label__", "")) language_codes.append(prediction.replace("__label__", ""))
return language_codes return language_codes
class STT: class STT:
samplerate = None samplerate = None
args = "" args = ""
@ -102,7 +92,9 @@ class STT:
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"] resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
) )
self.model = Model(rf"{vosk_model}") self.model = Model(
rf"{vosk_model}"
)
self.dump_fn = None self.dump_fn = None
self.q = gevent.queue.Queue() self.q = gevent.queue.Queue()
@ -140,9 +132,8 @@ class STT:
def stop_recognition(self): def stop_recognition(self):
self.is_running = False self.is_running = False
loadSettings()
if settings["STT"]["USE_STT"] and settings["STT"]["LANGUAGE"] != '': speech_recognition_service = STT()
speech_recognition_service = STT()
class TTS: class TTS:
@ -160,16 +151,16 @@ class TTS:
break break
self.engine.setProperty("voice", matching_id) self.engine.setProperty("voice", matching_id)
settings_folder = os.path.dirname(settingsPath)
if environment == "dev": if environment == "dev":
settings_folder = os.path.dirname(settingsPath)
src_folder = os.path.dirname(settings_folder) src_folder = os.path.dirname(settings_folder)
bot_folder = os.path.dirname(src_folder)
saveLocation = os.path.join( saveLocation = os.path.join(
bot_folder, "sounds", f"Internal_{count}.mp3" src_folder, "sounds\\tts", f"Internal_{count}.mp3"
) )
else: else:
resources_folder = os.path.dirname(settingsPath)
saveLocation = os.path.join( saveLocation = os.path.join(
settings_folder, "sounds", f"Internal_{count}.mp3" resources_folder, "sounds\\tts", f"Internal_{count}.mp3"
) )
self.engine.save_to_file(message, saveLocation) self.engine.save_to_file(message, saveLocation)
@ -184,13 +175,11 @@ class TTS:
return [voice.name for voice in voices] return [voice.name for voice in voices]
loadSettings()
if settings["TTS"]["USE_TTS"]: text_to_speech_service = TTS()
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():
@ -205,6 +194,14 @@ def stop_recording():
return Response("Speech recognition stopped", status=200) return Response("Speech recognition stopped", status=200)
# @app.before_request
# def custom_warning():
# if environment == "dev":
# print(
# # "Running in internal development environment. This server is not for production use."
# )
@app.route("/terminate", methods=["GET"]) @app.route("/terminate", methods=["GET"])
def terminate_processes(): def terminate_processes():
shutdown_server() shutdown_server()
@ -218,55 +215,35 @@ def shutdown_server():
func() func()
# @app.route("/detect", methods=["POST"])
# def server_status():
# try:
# request_data = request.json
# message = request_data.get("message", "")
# confidence_values = detector.compute_language_confidence_values(message)
# for language, value in confidence_values:
# print(f"{language.name}: {value:.2f}")
# message = request_data.get("message", "")
# except Exception as e:
# return jsonify({"error": "An error occurred"}), 500
# return jsonify({"message": "Audio triggered"}), 200
@app.route("/status", methods=["GET"]) @app.route("/status", methods=["GET"])
def server_status(): def server_status():
return jsonify({"status": "server is running"}) return jsonify({"status": "server is running"})
@app.route("/detect", methods=["POST"])
def get_language():
try:
request_data = request.json
message = request_data.get("message", "")
lang = LanguageDetection().predict_lang(message)
except Exception as e:
return jsonify({"error": "An error occurred"}), 500
return jsonify({"languages": lang}), 200
@app.route("/translate", methods=["POST"])
def get_translation():
loadSettings()
request_data = request.json
message = request_data.get("message", "")
detectedLanguage = request_data.get("language", "")
try:
# try:
translated = MyMemoryTranslator(
source=detectedLanguage, target=settings["LANGUAGE"]["TRANSLATE_TO"]
).translate(message)
# except Exception as e:
# return jsonify({"error": str(e), "code":429 }), 429
except Exception as e:
return jsonify({"error": str(e), "code":500 }), 500
return jsonify({"translation": translated}), 200
@app.route("/audio", methods=["POST"]) @app.route("/audio", methods=["POST"])
def trigger_backend_event(): 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(filteredMessage, voice, count) text_to_speech_service.say(message, voice, count)
except Exception as e: except Exception as e:
return jsonify({"error": e}), 500 return jsonify({"error": "An error occurred"}), 500
return jsonify({"message": "Audio triggered"}), 200 return jsonify({"message": "Audio triggered"}), 200
@ -276,16 +253,25 @@ def get_voices():
voices = text_to_speech_service.voices() voices = text_to_speech_service.voices()
return jsonify({"voices": voices}), 200 return jsonify({"voices": voices}), 200
except Exception as e: except Exception as e:
return jsonify({"error": e}), 500 return jsonify({"error": "An error occurred"}), 500
if __name__ == "__main__": if __name__ == "__main__":
LANGUAGE = LanguageDetection()
lang = LANGUAGE.predict_lang("hola cómo estás")
print(lang)
text = "Keep it up. You are awesome"
translated = MyMemoryTranslator(
source="english", target="spanish latin america"
).translate(text)
print(translated)
if len(sys.argv) > 1: if len(sys.argv) > 1:
loadSettings() settings.read(settingsPath)
port = int(settings["GENERAL"]["PORT"]) port = int(settings["GENERAL"]["PORT"])
else: else:
environment = "dev" environment = "dev"
port = 9000 port = 9000
stream_recognition() stream_recognition()
serve(app, host="0.0.0.0", port=port) app.run(host="127.0.0.1", port=port)
app.terminate()

View file

@ -1,469 +1,517 @@
@font-face { @font-face {
font-family: 'FRAMDCN'; font-family: 'FRAMDCN';
src: url(../fonts/FRAMCDN/FRAMDCN.woff);
} }
h1 { h1 {
font-family: 'FRAMDCN'; font-family: 'FRAMDCN';
} }
.message-window { .message-window {
height: calc(100% - 60px); height: calc(100% - 60px);
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
background-color: var(--mid-section); background-color: var(--mid-section);
padding-left: 50px; margin-left: 50px;
padding-right: 50px; font-family: 'FRAMDCN';
font-family: 'FRAMDCN'; position: relative;
position: relative; z-index: 1;
z-index: 1;
} }
.input-box { .input-box {
display: flex; display: flex;
border: none; border: none;
width: 100%; width: 100%;
height: 30px; height: 30px;
font-size: 16px; font-size: 16px;
} }
.userText { .userText {
color: var(--chat-bubble-message); color: var(--chat-bubble-message);
font-family: Helvetica; font-family: Helvetica;
font-size: 16px; font-size: 16px;
text-align: right; text-align: right;
clear: both; clear: both;
} }
.userText span { .userText span {
line-height: 1.5em; line-height: 1.5em;
display: inline-block; display: inline-block;
background: #5ca6fa; background: #5ca6fa;
padding: 10px; padding: 10px;
border-radius: 8px; border-radius: 8px;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
max-width: 80%; max-width: 80%;
margin-right: 10px; margin-right: 10px;
animation: floatup 0.5s forwards; animation: floatup 0.5s forwards;
} }
.botText { .botText {
color: #000; color: #000;
font-family: Helvetica; font-family: Helvetica;
font-weight: normal; font-weight: normal;
font-size: 16px; font-size: 16px;
text-align: left; text-align: left;
} }
.botText span { .botText span {
line-height: 1.5em; line-height: 1.5em;
display: inline-block; display: inline-block;
background: #e0e0e0; background: #e0e0e0;
padding: 10px; padding: 10px;
border-radius: 8px; border-radius: 8px;
border-bottom-left-radius: 2px; border-bottom-left-radius: 2px;
max-width: 80%; max-width: 80%;
margin-left: 10px; margin-left: 10px;
animation: floatup 0.5s forwards; animation: floatup 0.5s forwards;
} }
@keyframes floatup { @keyframes floatup {
from { from {
transform: translateY(14px); transform: translateY(14px);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateY(0px); transform: translateY(0px);
opacity: 1; opacity: 1;
} }
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.full-chat-block { .full-chat-block {
width: 100%; width: 100%;
border-radius: 0px; border-radius: 0px;
} }
.chat-bar-collapsible { .chat-bar-collapsible {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
width: 100%; width: 100%;
} }
.collapsible { .collapsible {
width: 100%; width: 100%;
border: 0px; border: 0px;
border-radius: 0px; border-radius: 0px;
} }
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 4px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background-color: #4c4c6a; background-color: #4c4c6a;
border-radius: 2px; border-radius: 2px;
} }
.chatBox { .chatBox {
width: 300px; width: 300px;
height: 400px; height: 400px;
max-height: 400px; max-height: 400px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
box-shadow: 0 0 4px var(--main-color4); box-shadow: 0 0 4px var(--main-color4);
} }
.chat-window { .chat-window {
flex: auto; flex: auto;
max-height: calc(100% - 60px); max-height: calc(100% - 60px);
background: #2f323b; background: #2f323b;
overflow: auto; overflow: auto;
} }
.chat-input { .chat-input {
height: 30px; height: 30px;
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
height: 60px; height: 60px;
background: var(--main-color3); background: var(--main-color3);
} }
.chat-input input { .chat-input input {
height: 59px; height: 59px;
line-height: 60px; line-height: 60px;
outline: 0 none; outline: 0 none;
border: none; border: none;
width: calc(100% - 60px); width: calc(100% - 60px);
color: var(--chat-bubble-message); color: var(--chat-bubble-message);
text-indent: 10px; text-indent: 10px;
font-size: 12pt; font-size: 12pt;
padding: 0; padding: 0;
background: var(--main-color3); background: var(--main-color3);
} }
.chat-input button { .chat-input button {
float: right; float: right;
outline: 0 none; outline: 0 none;
border: none; border: none;
background: var(--main-color3); background: var(--main-color3);
height: 40px; height: 40px;
width: 40px; width: 40px;
border-radius: 50%; border-radius: 50%;
padding: 2px 0 0 0; padding: 2px 0 0 0;
margin: 10px; margin: 10px;
} }
.chat-input input[good] + button { .chat-input input[good] + button {
box-shadow: box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
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: box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
0 8px 17px 0 rgba(0, 0, 0, 0.2), /* filter: brightness(150%); */
0 6px 20px 0 rgba(0, 0, 0, 0.19);
/* filter: brightness(150%); */
} }
.chat-input input[good] + button path { .chat-input input[good] + button path {
fill: var(--chat-bubble-message); fill: var(--chat-bubble-message);
} }
.msg-container { .msg-container {
direction: ltr; direction: ltr;
position: static; position: static;
width: 100%; display: inline-block;
padding: 10px 0px 0px 0px; width: 100%;
display: grid; padding: 0px 0px 10px 0px;
grid-template: 1fr / 1fr;
align-self: start;
} }
.msg-container > * { .msg-container-user {
grid-column: 1 / 1; direction: rtl;
grid-row: 1 / 1; position: static;
} display: inline-block;
width: 100%;
.msg-container.sender { margin-top: 10px;
place-items: self-start;
}
.msg-container.user {
place-items: self-end;
} }
.msg-box { .msg-box {
background: var(--chat-bubble); background: var(--chat-bubble);
color: white; color: white;
min-width: 100px; min-width: 150px;
border-radius: 5px; border-radius: 5px;
padding: 18px 5px 5px 5px; padding: 20px 5px 5px 25px;
box-shadow: margin: 20px 0px 0px 25px;
0 0 2px rgba(0, 0, 0, 0.12), box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
0 2px 4px rgba(0, 0, 0, 0.24); width: fit-content;
width: fit-content;
position: relative;
align-self: start;
} }
.msg-box.sender { .msg-box-user {
margin: 25px 25px 0px 35px; background: var(--chat-bubble);
} color: white;
text-align: -webkit-left;
.msg-box.user { min-width: 150px;
text-align: left; border-radius: 5px;
margin: 25px 35px 0px 0px; padding: 20px 15px 10px 5px;
margin: 0px 35px 0px 25px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
width: fit-content;
} }
.msg-box-user-temp { .msg-box-user-temp {
background: var(--chat-bubble-temp); background: var(--chat-bubble-temp);
} }
.user-img { .user-img {
display: inline-block; display: inline-block;
position: relative; border-radius: 50%;
border-radius: 50%; height: 50px;
height: 50px; width: 50px;
width: 50px; z-index: 5;
z-index: 5;
align-self: start;
} }
.messages.user { .user-img-user {
margin-right: 20px; display: inline-block;
border-radius: 50%;
height: 50px;
width: 50px;
position: absolute;
z-index: 5;
}
.messages {
margin-left: 20px;
}
.messages-user {
margin-right: 20px;
} }
.msg { .msg {
font-size: 12pt; font-size: 12pt;
color: var(--chat-bubble-message); color: var(--chat-bubble-message);
margin: 0 0 0 0; margin: 0 0 0 0;
} }
.msg-temp { .msg-temp {
color: var(--chat-bubble-message-temp); color: var(--chat-bubble-message-temp);
} }
/* .msg:first-of-type {
margin-top: 8px;
} */
.timestamp { .timestamp {
color: var(--chat-bubble-header); color: var(--chat-bubble-header);
font-size: 10pt; font-size: 10pt;
align-items: center; align-items: center;
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
} }
.timestamp-temp { .timestamp-temp {
color: var(--chat-bubble-header-temp); color: var(--chat-bubble-header-temp);
} }
.username { .username {
background-color: var(--main-color4); float: left;
color: white; background-color: var(--main-color4);
position: relative; margin-left: 25px;
border-radius: 5px; color: white;
z-index: 3; position: relative;
align-self: start; padding: 5px 5px 5px 30px;
border-radius: 5px;
top: 10px;
z-index: 1;
} }
.username.sender { .username-user {
padding: 0px 5px 5px 30px; background-color: var(--main-color4);
margin: 20px 5px 5px 25px; margin-right: 25px;
} color: white;
padding: 5px 40px 5px 15px;
.username.user { border-radius: 5px;
padding: 0px 30px 5px 5px; margin: 0px 30px 5px 5px;
margin: 20px 30px 5px 5px; top: 15px;
position: relative;
z-index: 1;
} }
.username-temp { .username-temp {
color: var(--chat-bubble-header-temp); color: var(--chat-bubble-header-temp);
} }
.post-time { .post-time {
font-size: 8pt; font-size: 8pt;
color: white; padding: 3px 5px 0px 15px;
display: inline-block; color: white;
background-color: var(--main-color4); display: inline-block;
position: relative; background-color: var(--main-color4);
z-index: 2; right: 15px;
border-radius: 5px; top: -19px;
align-self: start; position: relative;
} z-index: 2;
.post-time.sender { border-radius: 5px;
padding: 5px 5px 5px 15px; text-align: center;
margin: 0px 0px 0px 50px;
} }
.post-time.user { .post-time-user {
padding: 5px 15px 5px 5px; font-size: 8pt;
margin: 0px 50px 0px 0px; padding: 3px 15px 0px 5px;
margin: 5px -15px 0px -10px;
color: white;
display: inline-block;
background-color: var(--main-color4);
right: 60px;
top: -19px;
position: relative;
z-index: 2;
border-radius: 5px;
text-align: center;
} }
/* .msg-self .msg-box {
border-radius: 6px 6px 6px 6px;
background: var(--main-color1);
float: right;
}
.msg-self .user-img {
align-items: center;
}
.msg-self .msg {
text-align: justify;
text-justify: inter-word;
} */
.mmg { .mmg {
display: flex; display: flex;
}
.icon-container {
height: 50px;
position: absolute;
left: 0;
display: flex;
align-items: center;
}
.icon-container-user {
direction: ltr;
height: 50px;
position: absolute;
display: flex;
align-items: center;
} }
.img { .img {
height: 100%; height: 100%;
width: 100%; width: 100%;
border-radius: 50%; border-radius: 50%;
} }
.status-circle { .status-circle {
width: 20px; width: 20px;
border-radius: 50%; height: 20px;
z-index: 6; border-radius: 50%;
position: relative; margin-left: -15px;
align-self: start; z-index: 6;
margin-top: -30px;
} }
.status-circle.sender { .status-circle-user {
margin-left: 40px; width: 20px;
} height: 20px;
border-radius: 50%;
.status-circle.user { z-index: 6;
margin-right: 40px; margin-top: -30px;
} }
.menu-select { .menu-select {
font-size: 0.9rem; font-size: 0.9rem;
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background-color: var(--main-color3); background-color: var(--main-color3);
color: var(--main-color2); color: var(--main-color2);
align-items: center; align-items: center;
border: 0px; border: 0px;
padding-left: 10px; padding-left: 10px;
width: 300px; width: 300px;
font-size: 100%; font-size: 100%;
padding: 10px; padding: 10px;
padding-right: 25px; padding-right: 25px;
outline: none; outline: none;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='34' viewBox='0 0 24 24' width='32' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>"); background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='34' viewBox='0 0 24 24' width='32' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position-x: 100%; background-position-x: 100%;
background-position-y: 5px; background-position-y: 5px;
} }
.top-select { .top-select {
width: auto; width: auto;
height: 24px; height: 24px;
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
background-color: transparent; background-color: transparent;
color: white; color: white;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
border: none; border: none;
} }
.info-image { .info-image {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.top-select option { .top-select option {
margin: 40px; margin: 40px;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
color: #fff; color: #fff;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4); text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
background-color: var(--top-bar); background-color: var(--top-bar);
} }
.AdvancedMenu { .AdvancedMenu {
border: 1px var(--main-color2) solid; border: 1px var(--main-color2) solid;
margin-top: 10px; margin-top: 10px;
min-width: 555px; min-width: 555px;
border-radius: 5px; border-radius: 5px;
border-radius: 5px; border-radius: 5px;
} }
.legendStyle { .legendStyle {
margin-left: 1em; margin-left: 1em;
padding: 0.2em 0.8em; padding: 0.2em 0.8em;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.AdvancedMenuRow { .AdvancedMenuRow {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
justify-content: left; justify-content: left;
margin-bottom: 10px; margin-bottom: 10px;
} }
.AdvancedMenuLabel { .AdvancedMenuLabel {
font-size: 10pt; font-size: 10pt;
padding-right: 5px; padding-right: 5px;
margin-left: 10px; margin-left: 10px;
width: 125px; width: 125px;
} }
.AdvancedMenuLabel2 { .AdvancedMenuLabel2 {
font-size: 10pt; font-size: 10pt;
padding-right: 5px; padding-right: 5px;
margin-left: 10px; margin-left: 10px;
} }
.AdvancedMenuLabel3 { .AdvancedMenuLabel3 {
font-size: 12pt; font-size: 12pt;
padding-right: 5px; padding-right: 5px;
margin-left: 10px; margin-left: 10px;
} }
#SaveAdvancedSettingsButton { #SaveAdvancedSettingsButton {
margin-left: 10px; margin-left: 10px;
} }
.toggle { .toggle {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 60px; width: 60px;
height: 40px; height: 40px;
background-color: var(--main-color3); background-color: var(--main-color3);
border-radius: 20px; border-radius: 20px;
} }
/* After slide changes */ /* After slide changes */
.toggle:after { .toggle:after {
content: ''; content: '';
position: absolute; position: absolute;
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 50%; border-radius: 50%;
background-color: var(--main-color2); background-color: var(--main-color2);
left: 5px; left: 5px;
top: 5px; top: 5px;
} }
/* Checkbox checked effect */ /* Checkbox checked effect */
.checkbox:checked + .toggle::after { .checkbox:checked + .toggle::after {
left: 25px; left: 25px;
} }
/* Checkbox checked toggle label bg color */ /* Checkbox checked toggle label bg color */
.checkbox:checked + .toggle { .checkbox:checked + .toggle {
background-color: var(--main-color1); background-color: var(--main-color1);
} }
/* Checkbox vanished */ /* Checkbox vanished */
.checkbox { .checkbox {
display: none; display: none;
} }
/* Small toggle */ /* Small toggle */
@ -471,110 +519,36 @@ h1 {
/* toggle in label designing */ /* toggle in label designing */
.toggle-small { .toggle-small {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 30px; width: 30px;
height: 20px; height: 20px;
background-color: var(--main-color3); background-color: var(--main-color3);
border-radius: 10px; border-radius: 10px;
margin-left: 10px; margin-left: 10px;
} }
/* After slide changes */ /* After slide changes */
.toggle-small:after { .toggle-small:after {
content: ''; content: '';
position: absolute; position: absolute;
width: 15px; width: 15px;
height: 15px; height: 15px;
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
left: 2px; left: 2px;
top: 2px; top: 2px;
} }
/* Checkbox checked effect */ /* Checkbox checked effect */
.checkbox:checked + .toggle-small::after { .checkbox:checked + .toggle-small::after {
left: 13px; left: 13px;
} }
/* Checkbox checked toggle label bg color */ /* Checkbox checked toggle label bg color */
.checkbox:checked + .toggle-small { .checkbox:checked + .toggle-small {
background-color: var(--main-color1); background-color: var(--main-color1);
}
.emotes {
position: relative;
cursor: pointer;
}
.dark {
display: none;
position: absolute;
z-index: 1;
top: -400px;
}
.emotes:hover .dark {
display: block;
}
.fi {
position: relative;
z-index: 5;
border-radius: 50%;
}
.translation-header {
background-color: var(--main-color4);
border-radius: 5px;
width: fit-content;
padding: 5px;
margin: 10px 0px 5px -5px;
position: relative;
}
.translation-message {
position: relative;
margin: 20px 0px 0px 0px;
}
.translation-message.user {
margin: -20px 0px 0px 0px;
}
.translation-icon {
position: relative;
padding: 0px 0px 0px 0px;
margin: -45px 0px 0px -40px;
top: 30px;
}
.language-icon {
position: relative;
top: 45px;
}
.flag-icon {
width: 20px !important;
height: 20px !important;
left: 18px;
}
.flag-icon.user {
left: -18px;
top: -15px;
}
.user-flag {
left: unset;
right: 18px;
top: -65px;
}
.emote {
width: 28px;
height: 28px;
} }

View file

@ -1,62 +1,62 @@
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;
} }
label.btn span { 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;
font-size: 20px; font-size: 20px;
font-weight: normal; font-weight: normal;
line-height: 2em; line-height: 2em;
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: pointer;
background-color: none; background-color: none;
border-radius: 3px; border-radius: 3px;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
-o-user-select: none; -o-user-select: none;
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

@ -1,274 +1,253 @@
/* Basic styling */ /* Basic styling */
:root { :root {
overflow: hidden; overflow: hidden;
--main-color1: #6e2c8c; --main-color1: #6e2c8c;
--main-color1-temp: #6e2c8c; --main-color1-temp: #6e2c8c;
/*Left bar and top right bar*/ /*Left bar and top right bar*/
--main-color2: white; --main-color2: white;
--main-color2-temp: white; --main-color2-temp: white;
/*Icons and text*/ /*Icons and text*/
--main-color3: #211e1e; --main-color3: #211e1e;
--main-color3-temp: #211e1e; --main-color3-temp: #211e1e;
/*Buttons and input*/ /*Buttons and input*/
--main-color4: #2f2c34; --main-color4: #2f2c34;
--main-color4-temp: #2f2c34; --main-color4-temp: #2f2c34;
--top-bar: #100b12; --top-bar: #100b12;
--top-bar-temp: #100b12; --top-bar-temp: #100b12;
--mid-section: #352d3d; --mid-section: #352d3d;
--mid-section-temp: #352d3d; --mid-section-temp: #352d3d;
--chat-bubble: #7a6d7f; --chat-bubble: #7a6d7f;
--chat-bubble-header: #141414; --chat-bubble-header: #141414;
--chat-bubble-username: white; --chat-bubble-username: white;
--chat-bubble-message: white; --chat-bubble-message: white;
--chat-bubble-temp: #7a6d7f; --chat-bubble-temp: #7a6d7f;
--chat-bubble-header-temp: #141414; --chat-bubble-header-temp: #141414;
--chat-bubble-message-temp: white; --chat-bubble-message-temp: white;
} }
html { html {
box-sizing: border-box; box-sizing: border-box;
} }
*, *,
*:before, *:before,
*:after { *:after {
box-sizing: inherit; box-sizing: inherit;
}
html,
body {
height: 100%;
margin: 0;
/* border-top-left-radius: 20px; */
/* border-top-right-radius: 20px; */
overflow-x: hidden;
} }
body { body {
height: 100%; font-family: 'Segoe UI', sans-serif;
margin: 0; background: transparent;
font-family: 'Segoe UI', sans-serif; }
background: transparent;
position: relative; /* Styling of window frame and titlebar */
/* overflow-y: hidden;
overflow-x: hidden; */ body {
/* border: 1px solid #48545c; */
overflow-y: hidden;
} }
#titlebar { #titlebar {
display: block; display: block;
/* position: fixed; */ /* position: fixed; */
height: 32px; height: 32px;
width: calc(100%); width: calc(100%);
background-color: var(--top-bar); background-color: var(--top-bar);
/* border-top-left-radius: 20px; /* border-top-left-radius: 20px;
border-top-right-radius: 20px; */ border-top-right-radius: 20px; */
} }
.maximized #titlebar { .maximized #titlebar {
width: 100%; width: 100%;
padding: 0; padding: 0;
} }
#main { #main {
height: calc(100% - 32px); height: calc(100% - 32px);
margin-top: 32px; margin-top: 32px;
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
} }
#titlebar { #titlebar {
padding: 4px; padding: 4px;
} }
#titlebar #drag-region { #titlebar #drag-region {
width: 100%; width: 100%;
height: 100%; height: 100%;
-webkit-app-region: drag; -webkit-app-region: drag;
display: inline-flex; display: inline-flex;
} }
#titlebar { #titlebar {
color: var(--main-color2); color: var(--main-color2);
} }
#window-title { #window-title {
grid-column: 1; grid-column: 1;
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 8px; margin-left: 8px;
overflow: hidden; overflow: hidden;
font-family: 'Segoe UI', sans-serif; font-family: 'Segoe UI', sans-serif;
font-size: 12px; font-size: 12px;
} }
.maximized #window-title { .maximized #window-title {
margin-left: 12px; margin-left: 12px;
} }
#window-title span { #window-title span {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
line-height: 1.5; line-height: 1.5;
} }
#window-controls { #window-controls {
display: grid; display: grid;
grid-template-columns: repeat(3, 46px); grid-template-columns: repeat(3, 46px);
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
height: 32px; height: 32px;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
#window-controls .button { #window-controls .button {
grid-row: 1 / span 1; grid-row: 1 / span 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 32px; height: 32px;
} }
@media (-webkit-device-pixel-ratio: 1.5), @media (-webkit-device-pixel-ratio: 1.5),
(device-pixel-ratio: 1.5), (device-pixel-ratio: 1.5),
(-webkit-device-pixel-ratio: 2), (-webkit-device-pixel-ratio: 2),
(device-pixel-ratio: 2), (device-pixel-ratio: 2),
(-webkit-device-pixel-ratio: 3), (-webkit-device-pixel-ratio: 3),
(device-pixel-ratio: 3) { (device-pixel-ratio: 3) {
#window-controls .icon { #window-controls .icon {
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
} }
#window-controls .button { #window-controls .button {
user-select: none; user-select: none;
} }
#window-controls .button:hover { #window-controls .button:hover {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
/* filter: brightness(150%); */ /* filter: brightness(150%); */
} }
#window-controls .button:active { #window-controls .button:active {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
} }
#close-button:hover { #close-button:hover {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
/* border-top-right-radius: 20px; */ /* border-top-right-radius: 20px; */
background: #f1707a !important; background: #f1707a !important;
} }
#close-button:active { #close-button:active {
background: #f1707a !important; background: #f1707a !important;
} }
#close-button:active .icon { #close-button:active .icon {
filter: invert(1); filter: invert(1);
background: #f1707a !important; background: #f1707a !important;
} }
#min-button { #min-button {
grid-column: 1; grid-column: 1;
} }
#max-button, #max-button,
#restore-button { #restore-button {
grid-column: 2; grid-column: 2;
} }
#close-button { #close-button {
grid-column: 3; grid-column: 3;
} }
#restore-button { #restore-button {
display: none !important; display: none !important;
} }
.maximized #restore-button { .maximized #restore-button {
display: flex !important; display: flex !important;
} }
.maximized #max-button { .maximized #max-button {
display: none; display: none;
} }
.active-mic { .active-mic {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }
.about { .about {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
position: absolute; position: absolute;
left: 0; left: 0;
width: 32px; width: 32px;
text-align: -webkit-center; text-align: -webkit-center;
} }
.language-selector { .language-selector {
position: absolute; position: absolute;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
display: inline-block; display: inline-block;
background-color: transparent; background-color: transparent;
cursor: pointer; cursor: pointer;
font-family: font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'NotoColorEmojiLimited', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-apple-system, left: 50%;
BlinkMacSystemFont, transform: translateX(-50%);
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
left: 50%;
transform: translateX(-50%);
} }
.language-dropdown { .language-dropdown {
display: none; display: none;
position: absolute; position: absolute;
background-color: #fff; background-color: #fff;
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: font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'NotoColorEmojiLimited', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
} }
.language-item { .language-item {
padding: 5px; padding: 5px;
cursor: pointer; cursor: pointer;
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;
unicode-range: U+1F1E6-1F1FF; unicode-range: U+1F1E6-1F1FF;
src: url(https://raw.githack.com/googlefonts/noto-emoji/main/fonts/NotoColorEmoji.ttf); src: url(https://raw.githack.com/googlefonts/noto-emoji/main/fonts/NotoColorEmoji.ttf);
}
#drop-zone {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
text-align: center;
padding: 20px;
margin: 20px auto;
}
#dropped-file {
font-weight: bold;
} }

View file

@ -1,34 +1,34 @@
table { table {
margin-left: 10px; margin-left: 10px;
background-color: white; background-color: white;
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
margin-top: 60px; margin-top: 60px;
} }
th, th,
td { td {
border: 1px solid black; border: 1px solid black;
padding: 8px; padding: 8px;
text-align: left; text-align: left;
} }
.info { .info {
background-color: lightblue !important; background-color: lightblue !important;
} }
.warn { .warn {
background-color: #f39c12 !important; background-color: #f39c12 !important;
} }
.error { .error {
background-color: #e74c3c !important; background-color: #e74c3c !important;
} }
#logTable { #logTable {
width: 95%; width: 95%;
} }
#Logs { #Logs {
overflow: auto; overflow: auto;
} }

View file

@ -1,303 +1,226 @@
.container { .container {
display: flex; display: flex;
height: calc(100vh - 32px); height: calc(100vh - 32px);
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
/* will contain if #first is longer than #second */ /* will contain if #first is longer than #second */
} }
.mid { .mid {
flex: 3; flex: 3;
display: block; display: block;
position: relative; position: relative;
} }
.OptionPanel { .OptionPanel {
/* visibility: hidden; */ /* visibility: hidden; */
background-color: var(--mid-section); background-color: var(--mid-section);
flex: 3; flex: 3;
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.OptionPanel.show { .OptionPanel.show {
display: block; display: block;
overflow: auto; overflow: auto;
} }
.menu { .menu {
height: 100%; height: 100%;
display: block; display: block;
width: 100%; width: 100%;
background: var(--main-color1); background: var(--main-color1);
z-index: 1; z-index: 1;
position: relative; position: relative;
} }
.menu .items { .menu .items {
list-style: none; list-style: none;
margin: auto; margin: auto;
padding: 0; padding: 0;
} }
#rpe { #rpe {
font-size: 8pt; font-size: 8pt;
margin: 2px 0px 0px 0px; margin: 2px 0px 0px 0px;
} }
.menu .items .item { .menu .items .item {
width: 100%; width: 100%;
height: 50px; height: 50px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: var(--main-color1); background: var(--main-color1);
color: var(--main-color2); color: var(--main-color2);
}
.hdp:hover {
position: fixed;
/* filter: brightness(150%); */
} }
.menu .items .item-active { .menu .items .item-active {
background: -webkit-linear-gradient(left, var(--main-color2) 10%, var(--main-color2), var(--main-color1) 10%, var(--main-color1) 10%); background: -webkit-linear-gradient(left, var(--main-color2) 10%, var(--main-color2), var(--main-color1) 10%, var(--main-color1) 10%);
color: var(--main-color2); color: var(--main-color2);
filter: brightness(90%); filter: brightness(90%);
} }
.menu .items .item:hover { .menu .items .item:hover {
cursor: pointer; cursor: pointer;
color: var(--main-color2); color: var(--main-color2);
filter: brightness(120%); filter: brightness(120%);
} }
.sidepanel-left { .sidepanel-left {
position: relative; position: relative;
width: 50px; width: 50px;
font-size: 1.5em; font-size: 1.5em;
line-height: 1.5em; line-height: 1.5em;
font-family: Helvetica; font-family: Helvetica;
text-align: center; text-align: center;
box-shadow: box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
rgba(0, 0, 0, 0.16) 0px 3px 6px, transition: 0.3s ease-in-out;
rgba(0, 0, 0, 0.23) 0px 3px 6px;
transition: 0.3s ease-in-out;
} }
.sidepanel-right { .sidepanel-right {
position: relative; position: relative;
width: 200px; width: 200px;
font-size: 1.5em; font-size: 1.5em;
line-height: 1.5em; line-height: 1.5em;
font-family: Helvetica; font-family: Helvetica;
text-align: center; text-align: center;
box-shadow: box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
rgba(0, 0, 0, 0.16) 0px 3px 6px, transition: 0.3s ease-in-out;
rgba(0, 0, 0, 0.23) 0px 3px 6px;
transition: 0.3s ease-in-out;
} }
.collapse-menu-left { .collapse-menu-left {
margin-left: -50px; margin-left: -50px;
} }
.collapse-menu-right { .collapse-menu-right {
margin-right: -200px; margin-right: -200px;
} }
.sidepanel-left span { .sidepanel-left span {
position: relative; position: relative;
display: block; display: block;
} }
.sidepanel-right span { .sidepanel-right span {
position: relative; position: relative;
display: block; display: block;
} }
.circle-left { .circle-left {
position: absolute; position: absolute;
width: 50px; width: 50px;
height: 50px; height: 50px;
-webkit-clip-path: polygon(100% 0, 0 0, 0 100%); -webkit-clip-path: polygon(100% 0, 0 0, 0 100%);
clip-path: ellipse(68% 50% at 6% 50%); clip-path: ellipse(68% 50% at 6% 50%);
font-size: 20px; font-size: 20px;
background-color: var(--main-color3); background-color: var(--main-color3);
color: var(--main-color2); color: var(--main-color2);
justify-content: left; justify-content: left;
top: 32px; top: 32px;
left: 50px; left: 50px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
z-index: 2; z-index: 1;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }
.collapse-circle-left { .collapse-circle-left {
left: 0px; left: 0px;
} }
.circle-right { .circle-right {
position: absolute; position: absolute;
width: 50px; width: 50px;
height: 50px; height: 50px;
-webkit-clip-path: polygon(100% 100%, 0 0, 100% 0); -webkit-clip-path: polygon(100% 100%, 0 0, 100% 0);
clip-path: ellipse(68% 50% at 94% 50%); clip-path: ellipse(68% 50% at 94% 50%);
font-size: 20px; font-size: 20px;
background-color: var(--main-color3); background-color: var(--main-color3);
color: var(--main-color2); color: var(--main-color2);
justify-content: right; justify-content: right;
top: 32px; top: 32px;
right: 199px; right: 199px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
z-index: 1; z-index: 1;
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }
.collapse-circle-right { .collapse-circle-right {
right: 0px; right: 0px;
} }
.fa-cog { .fa-cog {
align-items: center; align-items: center;
align-self: center; align-self: center;
margin-left: 7px; margin-left: 7px;
} }
.fa-eye { .fa-eye {
align-self: center; align-self: center;
margin-right: 7px; margin-right: 7px;
} }
#min-button { #min-button {
grid-column: 1; grid-column: 1;
} }
#max-button, #max-button,
#restore-button { #restore-button {
grid-column: 2; grid-column: 2;
} }
#close-button { #close-button {
grid-column: 3; grid-column: 3;
} }
#mini-container { #mini-container {
width: 100%; width: 100%;
height: 300px; height: 300px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 25px; margin-bottom: 25px;
} }
#mini-main { #mini-main {
display: flex; display: flex;
height: 100%; height: 100%;
flex-direction: row; flex-direction: row;
} }
#mini-top-bar { #mini-top-bar {
height: 30px; height: 30px;
background-color: var(--top-bar-temp); background-color: var(--top-bar-temp);
/* border-top-left-radius: 20px; /* border-top-left-radius: 20px;
border-top-right-radius: 20px; */ border-top-right-radius: 20px; */
} }
#mini-left { #mini-left {
flex: 1; flex: 1;
background-color: var(--main-color1-temp); background-color: var(--main-color1-temp);
height: 100%; height: 100%;
} }
#mini-mid { #mini-mid {
flex: 8; flex: 8;
background-color: var(--mid-section-temp); background-color: var(--mid-section-temp);
height: 100%; height: 100%;
} }
#mini-right { #mini-right {
flex: 2; flex: 2;
background-color: var(--main-color4-temp); background-color: var(--main-color4-temp);
height: 100%; height: 100%;
}
.pop {
position: relative;
cursor: pointer;
}
.pop-selection {
width: 20px !important;
height: 20px !important;
}
.miniText {
position: absolute;
font-size: 8pt;
color: white;
padding: 3px;
}
.pop-content {
display: none;
position: absolute;
background-color: var(--main-color3);
z-index: 1;
height: 400px;
width: max-content;
overflow: auto;
grid-template-columns: repeat(3, 1fr);
top: -400px;
color: white;
border: 1px solid #444;
}
pop-content div {
color: white;
text-decoration: none;
display: block;
}
.pop-content div:hover {
backdrop-filter: invert(50%);
}
.pop:hover .pop-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.network-select {
position: relative;
cursor: pointer;
}
.send-to-channel {
display: none;
position: absolute;
background-color: var(--main-color3);
top: calc(-50% - 3px);
border: 1px solid #444;
z-index: 1;
width: 107px;
}
send-to-channel div {
color: white;
text-decoration: none;
display: block;
}
send-to-channel div:hover {
backdrop-filter: invert(50%);
}
.network-select:hover .send-to-channel {
display: block;
backdrop-filter: invert(50%);
grid-template-columns: repeat(3, 1fr);
}
.language-select {
position: relative;
} }

View file

@ -1,195 +1,179 @@
/*generated with Input range slider CSS style generator (version 20211225) /*generated with Input range slider CSS style generator (version 20211225)
https://toughengineer.github.io/demo/slider-styler*/ https://toughengineer.github.io/demo/slider-styler*/
input[type='range'].styled-slider { input[type='range'].styled-slider {
/* height: 500px; */ /* height: 500px; */
background: transparent; background: transparent;
-webkit-appearance: none; -webkit-appearance: none;
width: 300px; width: 300px;
} }
/*progress support*/ /*progress support*/
input[type='range'].styled-slider.slider-progress1 { input[type='range'].styled-slider.slider-progress1 {
--range: calc(var(--max) - var(--min)); --range: calc(var(--max) - var(--min));
--ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range));
--sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em));
} }
input[type='range'].styled-slider.slider-progress2 { input[type='range'].styled-slider.slider-progress2 {
--range: calc(var(--max) - var(--min)); --range: calc(var(--max) - var(--min));
--ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range));
--sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em));
} }
input[type='range'].styled-slider.slider-progress3 { input[type='range'].styled-slider.slider-progress3 {
--range: calc(var(--max) - var(--min)); --range: calc(var(--max) - var(--min));
--ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range));
--sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em));
} }
input[type='range'].styled-slider.slider-progress4 { input[type='range'].styled-slider.slider-progress4 {
--range: calc(var(--max) - var(--min)); --range: calc(var(--max) - var(--min));
--ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range)); --ratio: calc((var(--tiempotemporal) - var(--min)) / var(--range));
--sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em)); --sx: calc(0.5 * 2em + var(--ratio) * (100% - 2em));
} }
/*webkit*/ /*webkit*/
input[type='range'].styled-slider::-webkit-slider-thumb { input[type='range'].styled-slider::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
width: 2em; width: 2em;
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background: #ffffff; background: #ffffff;
border: none; border: none;
box-shadow: 0 0 2px black; box-shadow: 0 0 2px black;
margin-top: calc(2em * 0.5 - 2em * 0.5); margin-top: calc(2em * 0.5 - 2em * 0.5);
} }
input[type='range'].styled-slider::-webkit-slider-runnable-track { input[type='range'].styled-slider::-webkit-slider-runnable-track {
height: 40px; height: 40px;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
background: #1a1a1a; background: #1a1a1a;
box-shadow: none; box-shadow: none;
} }
input[type='range'].styled-slider.slider-progress1::-webkit-slider-runnable-track { input[type='range'].styled-slider.slider-progress1::-webkit-slider-runnable-track {
background: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#1a1a1a;
} }
/*mozilla*/ /*mozilla*/
input[type='range'].styled-slider::-moz-range-thumb { input[type='range'].styled-slider::-moz-range-thumb {
width: 2em; width: 2em;
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background: #ffffff; background: #ffffff;
border: none; border: none;
box-shadow: 0 0 2px black; box-shadow: 0 0 2px black;
} }
input[type='range'].styled-slider::-moz-range-track { input[type='range'].styled-slider::-moz-range-track {
height: 40px; height: 40px;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
background: var(--main-color3); background: var(--main-color3);
box-shadow: none; box-shadow: none;
} }
input[type='range'].styled-slider.slider-progress1::-moz-range-track { input[type='range'].styled-slider.slider-progress1::-moz-range-track {
background: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
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: background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
#464646;
} }
/*ms*/ /*ms*/
input[type='range'].styled-slider::-ms-fill-upper { input[type='range'].styled-slider::-ms-fill-upper {
background: transparent; background: transparent;
border-color: transparent; border-color: transparent;
} }
input[type='range'].styled-slider::-ms-fill-lower { input[type='range'].styled-slider::-ms-fill-lower {
background: transparent; background: transparent;
border-color: transparent; border-color: transparent;
} }
input[type='range'].styled-slider::-ms-thumb { input[type='range'].styled-slider::-ms-thumb {
width: 2em; width: 2em;
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background: #ffffff; background: #ffffff;
border: none; border: none;
box-shadow: 0 0 2px black; box-shadow: 0 0 2px black;
margin-top: 0; margin-top: 0;
box-sizing: border-box; box-sizing: border-box;
} }
input[type='range'].styled-slider::-ms-track { input[type='range'].styled-slider::-ms-track {
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background: var(--main-color3); background: var(--main-color3);
border: none; border: none;
box-shadow: none; box-shadow: none;
box-sizing: border-box; box-sizing: border-box;
} }
input[type='range'].styled-slider.slider-progress1::-ms-fill-lower { input[type='range'].styled-slider.slider-progress1::-ms-fill-lower {
height: 40px; height: 40px;
border-radius: 1em 0 0 1em; border-radius: 1em 0 0 1em;
margin: -undefined 0 -undefined -undefined; margin: -undefined 0 -undefined -undefined;
background: var(--main-color1); background: var(--main-color1);
border: none; border: none;
border-right-width: 0; border-right-width: 0;
} }
input[type='range'].styled-slider.slider-progress2::-ms-fill-lower { input[type='range'].styled-slider.slider-progress2::-ms-fill-lower {
height: 40px; height: 40px;
border-radius: 1em 0 0 1em; border-radius: 1em 0 0 1em;
margin: -undefined 0 -undefined -undefined; margin: -undefined 0 -undefined -undefined;
background: var(--main-color1); background: var(--main-color1);
border: none; border: none;
border-right-width: 0; border-right-width: 0;
} }
input[type='range'].styled-slider.slider-progress3::-ms-fill-lower { input[type='range'].styled-slider.slider-progress3::-ms-fill-lower {
height: 40px; height: 40px;
border-radius: 1em 0 0 1em; border-radius: 1em 0 0 1em;
margin: -undefined 0 -undefined -undefined; margin: -undefined 0 -undefined -undefined;
background: var(--main-color1); background: var(--main-color1);
border: none; border: none;
border-right-width: 0; border-right-width: 0;
} }
input[type='range'].styled-slider.slider-progress4::-ms-fill-lower { input[type='range'].styled-slider.slider-progress4::-ms-fill-lower {
height: 40px; height: 40px;
border-radius: 1em 0 0 1em; border-radius: 1em 0 0 1em;
margin: -undefined 0 -undefined -undefined; margin: -undefined 0 -undefined -undefined;
background: var(--main-color1); background: var(--main-color1);
border: none; border: none;
border-right-width: 0; border-right-width: 0;
} }
.inputBox { .inputBox {
border: none; border: none;
width: 38px; width: 38px;
border-radius: 10px; border-radius: 10px;
text-align: center; text-align: center;
font-size: 14pt; font-size: 14pt;
font-weight: bold; font-weight: bold;
} }

View file

@ -1,419 +1,441 @@
.viewer-list { .viewer-list {
background-color: var(--main-color4); background-color: var(--main-color4);
height: 100%; height: 100%;
flex: 1; flex: 1;
font-size: 14px; font-size: 14px;
height: 100%; height: 100%;
display: block; display: block;
} }
ul.no-bullets { ul.no-bullets {
list-style-type: none; list-style-type: none;
list-style-position: outside; list-style-position: outside;
margin: 0%; margin: 0%;
padding: 0%; padding: 0%;
padding-left: 5px; padding-left: 5px;
text-align: left; text-align: left;
} }
li { li {
line-height: 1em; line-height: 1em;
} }
h1.titles { h1.titles {
margin: 0px; margin: 0px;
padding: 0%; padding: 0%;
text-align: left; text-align: left;
padding-left: 5px; padding-left: 5px;
} }
main { main {
background: var(--main-color1); background: var(--main-color1);
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2); box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
height: 32px; height: 32px;
} }
main label { main label {
text-align: center; text-align: center;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
p { p {
font-size: 13px; font-size: 13px;
} }
main input { main input {
display: none; display: none;
} }
section { section {
clear: both; clear: both;
padding-top: 10px; padding-top: 10px;
display: none; display: none;
height: calc(100% - 60px); height: calc(100% - 60px);
} }
.Basiclabel { .Basiclabel {
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
display: block; display: block;
float: left; float: left;
flex: auto; flex: auto;
font-size: 20px; font-size: 20px;
padding: 0px 10px; padding: 0px 10px;
width: 50px; width: 50px;
align-self: baseline; align-self: baseline;
text-align: center; text-align: center;
} }
.scale { .scale {
height: 2em; height: 2em;
width: 2em; width: 2em;
vertical-align: bottom; vertical-align: bottom;
} }
.tab { .tab {
/* filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); */ /* filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); */
align-items: flex-start; align-items: flex-start;
margin: auto; margin: auto;
cursor: pointer; cursor: pointer;
} }
input:checked + label { input:checked + label {
border-top-color: #ffb03d; border-top-color: #ffb03d;
border-right-color: #ddd; border-right-color: #ddd;
border-left-color: #ddd; border-left-color: #ddd;
border-bottom-color: transparent; border-bottom-color: transparent;
text-decoration: none; text-decoration: none;
} }
/* --------------------------------- */ /* --------------------------------- */
.radius { .radius {
border-radius: 5px; border-radius: 5px;
} }
.radius .tabx:active::before { .radius .tabx:active::before {
border-radius: 5px !important; border-radius: 5px !important;
} }
.tabx-bar { .tabx-bar {
background-color: var(--main-color1); background-color: var(--main-color1);
/* padding: 5px; */ /* padding: 5px; */
box-shadow: 1px 4px 20px rgba(0, 0, 0, 0.2); box-shadow: 1px 4px 20px rgba(0, 0, 0, 0.2);
display: flex; display: flex;
/* margin: 10px; */ /* margin: 10px; */
color: black; color: black;
height: 50px; height: 50px;
} }
.tabx-bar .tabx { .tabx-bar .tabx {
box-sizing: border-box; box-sizing: border-box;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
width: 70px; width: 70px;
height: 50px; height: 50px;
background: inherit; background: inherit;
display: inherit; display: inherit;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;
transition: all 0.3s; transition: all 0.3s;
} }
.tabx-bar .tabx::before { .tabx-bar .tabx::before {
position: absolute; position: absolute;
content: ''; content: '';
width: 26%; width: 26%;
height: 13%; height: 13%;
border-top-left-radius: 200px; border-top-left-radius: 200px;
border-top-right-radius: 200px; border-top-right-radius: 200px;
border-bottom: none; border-bottom: none;
background-color: #607d8b; background-color: #607d8b;
/* bottom: -8px; */ /* bottom: -8px; */
opacity: 0; opacity: 0;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
.tabx-bar .tabx:active::before { .tabx-bar .tabx:active::before {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #5fef8d; background-color: #5fef8d;
border-radius: 0; border-radius: 0;
} }
.tabx-bar .tabx:hover::before { .tabx-bar .tabx:hover::before {
opacity: 1; opacity: 1;
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;
} }
.tabx-bar .tabx:hover::after { .tabx-bar .tabx:hover::after {
opacity: 1; opacity: 1;
top: 6px; top: 6px;
} }
.tabx-bar .tabx.selected { .tabx-bar .tabx.selected {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
border-radius: inherit; border-radius: inherit;
padding-top: 0px; padding-top: 0px;
} }
.tabx-bar .tabx.selected::after { .tabx-bar .tabx.selected::after {
opacity: 1; opacity: 1;
top: -10px; top: -10px;
} }
.tabx-bar .tabx.selected::before { .tabx-bar .tabx.selected::before {
opacity: 1; opacity: 1;
bottom: 0px; bottom: 0px;
} }
.tabx-bar .tabx .icon { .tabx-bar .tabx .icon {
color: inherit; color: inherit;
font-size: 24px; font-size: 24px;
display: inherit; display: inherit;
} }
.tabx-bar .tabx .icon img { .tabx-bar .tabx .icon img {
margin: auto; margin: auto;
height: 32 px; height: 32 px;
} }
.language { .language {
width: 80px; width: 80px;
text-align: center; text-align: center;
} }
#AdvancedMenu_mask { #AdvancedMenu_mask {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
visibility: hidden; visibility: hidden;
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
background-color: var(--mid-section); background-color: var(--mid-section);
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
padding-left: 50px; padding-left: 50px;
padding-right: 50px; padding-right: 50px;
} }
#ThemeCreator_mask { #ThemeCreator_mask {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
margin-top: 0px; margin-top: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
visibility: hidden; visibility: hidden;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
background-color: var(--mid-section); background-color: var(--mid-section);
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
padding-left: 50px; padding-left: 50px;
padding-right: 50px; padding-right: 50px;
} }
.fname { .fname {
background: var(--main-color3); background: var(--main-color3);
border: none; border: none;
height: 40px; height: 40px;
border-radius: 40px; border-radius: 40px;
width: 300px; width: 300px;
outline: none; outline: none;
color: var(--main-color2); color: var(--main-color2);
font-size: 10pt; font-size: 10pt;
padding-left: 10px; padding-left: 10px;
} }
input[type='password'] { input[type='password'] {
background: var(--main-color3); background: var(--main-color3);
border: none; border: none;
height: 40px; height: 40px;
border-radius: 40px; border-radius: 40px;
width: 300px; width: 300px;
outline: none; outline: none;
color: var(--main-color2); color: var(--main-color2);
font-size: 10pt; font-size: 10pt;
padding-left: 10px; padding-left: 10px;
padding-right: 40px; padding-right: 40px;
/* To make space for the reveal button */ /* To make space for the reveal button */
} }
input[type='url'] { input[type='url'] {
background: var(--main-color3); background: var(--main-color3);
border: none; border: none;
height: 40px; height: 40px;
border-radius: 40px; border-radius: 40px;
width: 260px; width: 260px;
outline: none; outline: none;
color: var(--main-color2); color: var(--main-color2);
font-size: 10pt; font-size: 10pt;
margin-left: 10px; margin-left: 10px;
} }
input[type='lol'] { input[type='lol'] {
background: var(--main-color3); background: var(--main-color3);
border: none; border: none;
height: 40px; height: 40px;
border-radius: 40px; border-radius: 40px;
width: 300px; width: 300px;
outline: none; outline: none;
color: var(--main-color2); color: var(--main-color2);
font-size: 10pt; font-size: 10pt;
padding-left: 10px; padding-left: 10px;
padding-right: 40px; padding-right: 40px;
/* To make space for the reveal button */ /* To make space for the reveal button */
} }
/* Style for the reveal button */ /* Style for the reveal button */
.password-toggle-btn { .password-toggle-btn {
position: absolute; position: absolute;
background-color: transparent; background-color: transparent;
border: none; border: none;
cursor: pointer; cursor: pointer;
left: 450px; left: 450px;
} }
/* Hide the default appearance of the button */ /* Hide the default appearance of the button */
.password-toggle-btn::-moz-focus-inner { .password-toggle-btn::-moz-focus-inner {
border: 0; border: 0;
} }
/* Style the reveal icon (you can use your preferred icon or font) */ /* Style the reveal icon (you can use your preferred icon or font) */
.password-toggle-icon { .password-toggle-icon {
font-size: 16px; font-size: 16px;
color: var(--main-color2); color: var(--main-color2);
} }
#toasts { #toasts {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
/* Adjust the distance from the bottom of the screen */ /* Adjust the distance from the bottom of the screen */
right: 0%; right: 0%;
/* Center the toasts horizontally */ /* Center the toasts horizontally */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
z-index: 999; z-index: 999;
} }
.toast { .toast {
background-color: #333; background-color: #333;
color: white; color: white;
border-radius: 5px; border-radius: 5px;
padding: 1rem 2rem; padding: 1rem 2rem;
margin: 0.5rem; margin: 0.5rem;
opacity: 0; opacity: 0;
transform: translateY(100%); transform: translateY(100%);
animation: animation: toastAnimation 0.5s ease-in-out forwards, toastDisappear 0.5s ease-in-out 9s forwards;
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 */
.info { .info {
background-color: lightblue !important; background-color: lightblue !important;
} }
.success { .success {
background-color: #2ecc71 !important; background-color: #2ecc71 !important;
} }
.warning { .warning {
background-color: #f39c12 !important; background-color: #f39c12 !important;
} }
.error { .error {
background-color: #e74c3c !important; background-color: #e74c3c !important;
} }
/* CSS animation for the toast appearance */ /* CSS animation for the toast appearance */
@keyframes toastAnimation { @keyframes toastAnimation {
from { from {
opacity: 0; opacity: 0;
transform: translateY(100%); transform: translateY(100%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
} }
/* CSS animation for the toast disappearance */ /* CSS animation for the toast disappearance */
@keyframes toastDisappear { @keyframes toastDisappear {
from { from {
opacity: 1; opacity: 1;
} }
to { to {
opacity: 0; opacity: 0;
} }
} }
.menu-icon { .menu-icon {
font-size: 17pt; font-size: 17pt;
} }
.tooltip { .tooltip {
position: absolute; position: absolute;
font-size: 12pt; display: inline-block;
padding: 5px; visibility: hidden;
background: var(--main-color3); font-size: 12px;
border-radius: 5px; line-height: 20px;
visibility: hidden; padding: 5px;
box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2); background: var(--main-color3);
color: var(--main-color2); border-radius: 5px;
font-family: 'xxii_avenmedium'; visibility: hidden;
z-index: 999; opacity: 1;
max-width: 200px; box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2);
width: max-content; transition: opacity 0.3s, visibility 0s;
color: var(--main-color2);
font-family: 'xxii_avenmedium';
z-index: 999;
} }
/* .tooltip .tooltiptext {
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
}
.tooltip .tooltiptext::after {
content: "";
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: black transparent transparent transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
} */
div[type='text']:disabled { div[type='text']:disabled {
background: #4b4b4b; background: #4b4b4b;
display: none; display: none;
} }
input[type='text']:disabled { input[type='text']:disabled {
background: #4b4b4b; background: #4b4b4b;
} }
button[type='text']:disabled { button[type='text']:disabled {
background: #4b4b4b; background: #4b4b4b;
display: none; display: none;
} }
input[type2='text']:disabled { input[type2='text']:disabled {
background: #4b4b4b; background: #4b4b4b;
} }
div:disabled { div:disabled {
background: #4b4b4b; background: #4b4b4b;
filter: brightness(200%); filter: brightness(200%);
} }
div:disabled { div:disabled {
background: #4b4b4b; background: #4b4b4b;
filter: brightness(200%); filter: brightness(200%);
} }

View file

@ -1,88 +0,0 @@
.token-autocomplete-container {
display: inline-flex;
flex-wrap: wrap;
width: 300px;
}
.token-autocomplete-container,
.token-autocomplete-container * {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
}
.token-autocomplete-container .token-autocomplete-input {
color: var(--main-color2);
font-size: 16px;
line-height: 32px;
margin: 4px 2px;
padding: 0px 8px;
height: 40px;
width: 300px;
background-color: var(--main-color3);
border-radius: 20px;
}
.token-autocomplete-container .token-autocomplete-input:empty::before {
content: attr(data-placeholder);
color: gray;
}
.token-autocomplete-container .token-autocomplete-token {
font-size: 16px;
line-height: 32px;
background-color: var(--main-color3);
margin: 4px 2px;
border-radius: 32px;
padding: 0px 8px;
pointer-events: none;
}
.token-autocomplete-container .token-autocomplete-token:hover {
background-color: #ef9a9a;
}
.token-autocomplete-container .token-autocomplete-token .token-autocomplete-token-delete {
cursor: pointer;
font-size: 24px;
line-height: 16px;
margin-left: 4px;
pointer-events: auto;
border-radius: 50%;
height: 24px;
width: 24px;
display: inline-block;
text-align: center;
}
.token-autocomplete-container .token-autocomplete-token .token-autocomplete-token-delete:hover {
background-color: #e55858;
}
.token-autocomplete-container .token-autocomplete-suggestions {
display: none;
width: 100%;
list-style-type: none;
padding: 0px;
margin: 0px;
}
.token-autocomplete-container .token-autocomplete-suggestions li {
width: 100%;
padding: 8px;
cursor: pointer;
}
.token-autocomplete-container .token-autocomplete-suggestions li.token-autocomplete-suggestion-active {
color: #747474;
background-color: #fdfdfd;
}
.token-autocomplete-container .token-autocomplete-suggestions li.token-autocomplete-suggestion-highlighted {
background-color: #95caec;
}
.token-autocomplete-container .token-autocomplete-suggestions li .token-autocomplete-suggestion-description {
display: block;
font-size: 0.7em;
color: #808080;
}

View file

@ -1,163 +1,161 @@
#tstx { #tstx {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-left: 40px; margin-left: 40px;
} }
.optionrow { .optionrow {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
justify-content: left; justify-content: left;
margin-bottom: 10px; margin-bottom: 10px;
} }
.LabelText { .LabelText {
margin-bottom: 5px; margin-bottom: 5px;
color: var(--main-color2); color: var(--main-color2);
margin: 0px 0px 5px 0px; margin: 0px 0px 5px 0px;
} }
#volume-icon { #volume-icon {
color: var(--main-color2); color: var(--main-color2);
scale: 0.75; scale: 0.75;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
align-self: center; align-self: center;
} }
#image { #image {
width: 100px; width: 100px;
height: 100px; height: 100px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.TTSVolume { .TTSVolume {
color: var(--main-color2); color: var(--main-color2);
font-size: 12pt; font-size: 12pt;
text-align: center; text-align: center;
} }
#SoundVolume { #SoundVolume {
color: var(--main-color2); color: var(--main-color2);
font-size: 12pt; font-size: 12pt;
} }
.testLabel { .testLabel {
color: var(--main-color2); color: var(--main-color2);
font-size: 12pt; font-size: 12pt;
} }
textarea { textarea {
height: 60px; height: 60px;
padding: 5px; padding: 5px;
width: 300px; width: 300px;
resize: none; resize: none;
border-radius: 5px; border-radius: 5px;
background: var(--main-color3); background: var(--main-color3);
color: var(--main-color2); color: var(--main-color2);
font-family: 'xxii_avenmedium'; font-family: 'xxii_avenmedium';
border: none; border: none;
} }
.SaveConfig { .SaveConfig {
align-content: center; align-content: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
color: var(--main-color2); color: var(--main-color2);
margin-bottom: 10px; margin-bottom: 10px;
} }
.SmallButton { .SmallButton {
color: var(--main-color2); color: var(--main-color2);
width: 50px; width: 50px;
cursor: pointer; cursor: pointer;
text-shadow: text-shadow: 0 0 5px #070607, 0 0 5px #070607, 0 0 5px #070607;
0 0 5px #070607, /* transition: all 0.15s ease-in-out; */
0 0 5px #070607, text-align: center;
0 0 5px #070607;
transition: all 0.15s ease-in-out;
text-align: center;
} }
.SmallButton:hover { .SmallButton:hover {
color: var(--main-color1); /* color: var(--main-color1); */
cursor: pointer; width: 50px;
cursor: pointer;
/* filter: brightness(150%); */
} }
.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;
} }
.AdvancedMenuButton { .AdvancedMenuButton {
width: 300px; width: 300px;
height: 40px; height: 40px;
border-radius: 20px; border-radius: 20px;
background-color: var(--main-color3); background-color: var(--main-color3);
color: var(--main-color2); color: var(--main-color2);
padding: 0; padding: 0;
border: none; border: none;
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: transition: box-shadow 0.3s ease, background-color 0.3s ease;
box-shadow 0.3s ease, /* Add a smooth transition for box-shadow and background-color */
background-color 0.3s ease;
/* Add a smooth transition for box-shadow and background-color */
} }
.AdvancedMenuButton:hover { .AdvancedMenuButton:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
background-color: var(--main-color3); background-color: var(--main-color3);
filter: brightness(150%); filter: brightness(150%);
/* Darken the background color on hover */ /* Darken the background color on hover */
cursor: pointer; cursor: pointer;
} }
.AdvancedMenuButton:active { .AdvancedMenuButton:active {
transform: translateY(2px); transform: translateY(2px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
background-color: var(--main-color3); background-color: var(--main-color3);
/* Reset the background color on click */ /* Reset the background color on click */
} }
.AdvancedMenuIcon { .AdvancedMenuIcon {
filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%); filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(350deg) brightness(104%) contrast(101%);
align-items: flex-start; align-items: flex-start;
margin: auto; margin: auto;
height: 24px; height: 24px;
width: 24px; width: 24px;
} }
.AdvancedMenuIcon2 { .AdvancedMenuIcon2 {
align-items: flex-start; align-items: flex-start;
margin: auto; margin: auto;
height: 24px; height: 24px;
width: 24px; width: 24px;
} }
input:hover { input:hover {
filter: brightness(120%); filter: brightness(120%);
} }
select:hover { select:hover {
filter: brightness(120%); filter: brightness(120%);
} }
textarea:hover { textarea:hover {
filter: brightness(120%); filter: brightness(120%);
} }
label:hover { label:hover {
filter: brightness(120%); filter: brightness(120%);
} }
.circle-right:hover { .circle-right:hover {
filter: brightness(120%); filter: brightness(120%);
} }
.circle-left:hover { .circle-left:hover {
filter: brightness(120%); filter: brightness(120%);
} }

View file

@ -1,87 +1,87 @@
.middle { .middle {
width: 100%; width: 100%;
height: 50px; height: 50px;
border-radius: 10px; border-radius: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.slider-container { .slider-container {
width: 300px; width: 300px;
height: 100%; height: 100%;
position: relative; position: relative;
} }
.slider-container .bar { .slider-container .bar {
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 10px; border-radius: 10px;
background-color: var(--main-color3); background-color: var(--main-color3);
} }
.slider-container .bar .fill { .slider-container .bar .fill {
display: block; display: block;
width: 50%; width: 50%;
height: 100%; height: 100%;
border-radius: inherit; border-radius: inherit;
background-color: var(--main-color1); background-color: var(--main-color1);
} }
.slider-container .slider { .slider-container .slider {
position: absolute; position: absolute;
top: 50%; top: 50%;
-webkit-appearance: none; -webkit-appearance: none;
margin: 0; margin: 0;
width: 100%; width: 100%;
height: 0; height: 0;
border-radius: 5px; border-radius: 5px;
outline: none; outline: none;
background-color: transparent; background-color: transparent;
} }
.slider-container .slider::-webkit-slider-thumb { .slider-container .slider::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
width: 30px; width: 30px;
height: 30px; height: 30px;
background-color: var(--main-color2); background-color: var(--main-color2);
border-top-left-radius: 50%; border-top-left-radius: 50%;
border-top-right-radius: 50%; border-top-right-radius: 50%;
border-bottom-right-radius: 50%; border-bottom-right-radius: 50%;
border-bottom-left-radius: 50%; border-bottom-left-radius: 50%;
transform: rotate(-45deg) translate(0%, 0%); transform: rotate(-45deg) translate(0%, 0%);
cursor: pointer; cursor: pointer;
outline: none; outline: none;
/* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */ /* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */
box-shadow: 0 0 3px rgb(0 0 0 / 10%); box-shadow: 0 0 3px rgb(0 0 0 / 10%);
/* transition: .3s ease-in-out; */ /* transition: .3s ease-in-out; */
} }
.slider-container .slider:active::-webkit-slider-thumb, .slider-container .slider:active::-webkit-slider-thumb,
.slider-container .slider::-webkit-slider-thumb:hover { .slider-container .slider::-webkit-slider-thumb:hover {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
transform: rotate(-45deg) translate(50%, -50%); transform: rotate(-45deg) translate(50%, -50%);
} }
.slider-container .slider:active::-webkit-slider-thumb { .slider-container .slider:active::-webkit-slider-thumb {
box-shadow: 0 0 0 3px rgba(255, 255, 255, 1); box-shadow: 0 0 0 3px rgba(255, 255, 255, 1);
} }
.option-icon-container { .option-icon-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* justify-content: center; */ /* justify-content: center; */
align-items: center; align-items: center;
/* width: 78px; */ /* width: 78px; */
/* float: right; */ /* float: right; */
flex-grow: 1; flex-grow: 1;
} }
.brush-icon-container { .brush-icon-container {
margin-left: -20px; margin-left: -20px;
font-size: 20pt; font-size: 20pt;
} }
.icon { .icon {
fill: var(--main-color2); fill: var(--main-color2);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

File diff suppressed because it is too large Load diff

View file

@ -1,118 +1,118 @@
/* 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');
function getAmazonVoices() { function getAmazonVoices() {
if (!settings.AMAZON.USE_AMAZON) { if (!settings.AMAZON.USE_AMAZON) {
callback(); callback();
return; return;
} }
addVoiceService('Amazon'); addVoiceService('Amazon');
const primaryVoice = document.querySelector('#primaryAmazonVoice'); let primaryVoice = document.querySelector('#primaryAmazonVoice');
const secondaryVoice = document.querySelector('#secondaryAmazonVoice'); let 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');
option.value = voice; option.value = voice;
option.innerHTML = voice; option.innerHTML = voice;
voiceSelect.appendChild(option); voiceSelect.appendChild(option);
}); });
} }
setVoicesinSelect(primaryVoice); setVoicesinSelect(primaryVoice);
primaryVoice.value = settings.AMAZON.PRIMARY_VOICE; primaryVoice.value = settings.AMAZON.PRIMARY_VOICE;
setVoicesinSelect(secondaryVoice); setVoicesinSelect(secondaryVoice);
secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE; secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE;
} }
if (settings.AMAZON.USE_AMAZON) { if (settings.AMAZON.USE_AMAZON) {
getAmazonVoices(); getAmazonVoices();
} }
class PollyTTS { class PollyTTS {
textToSpeech(options, callback) { constructor() {}
if (!options) {
return callback(new Error('Options are missing')); textToSpeech(options, callback) {
if (!options) {
return callback(new Error('Options are missing'));
}
const qs = {
Text: options.text,
TextType: options.textType || 'text',
VoiceId: options.voiceId || 'Mia',
SampleRate: options.sampleRate || 22050,
OutputFormat: options.outputFormat || 'mp3',
Engine: options.engine || 'neural',
};
const opts = {
service: 'polly',
region: options.region || 'us-east-1',
path: `/v1/speech?${querystring.stringify(qs)}`,
signQuery: true,
};
// you can also pass AWS credentials in explicitly (otherwise taken from process.env)
aws4.sign(opts, this.credentials);
https
.get(opts, (res) => {
if (res.statusCode !== 200) {
return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`));
}
callback(null, res);
return true;
})
.on('error', (e) => {
callback(e);
});
return null;
} }
const qs = { describeVoices(callback, credentials) {
Text: options.text, this.credentials = credentials;
TextType: options.textType || 'text', const qs = {
VoiceId: options.voiceId || 'Mia', Engine: 'neural',
SampleRate: options.sampleRate || 22050, };
OutputFormat: options.outputFormat || 'mp3',
Engine: options.engine || 'neural'
};
const opts = { const opts = {
service: 'polly', service: 'polly',
region: options.region || 'us-east-1', region: 'us-east-1',
path: `/v1/speech?${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}`));
} }
callback(null, res);
return true;
})
.on('error', e => {
callback(e);
});
return null; let body = '';
} res.on('readable', () => {
body += res.read();
});
res.on('end', () => {
callback(null, body);
});
describeVoices(callback, credentials) { return undefined;
this.credentials = credentials; })
const qs = { .on('error', (e) => {
Engine: 'neural' callback(e);
}; });
const opts = { return null;
service: 'polly', }
region: 'us-east-1',
path: `/v1/voices?${querystring.stringify(qs)}`,
signQuery: true
};
// you can also pass AWS credentials in explicitly (otherwise taken from process.env)
aws4.sign(opts, this.credentials);
https
.get(opts, res => {
if (res.statusCode !== 200) {
return callback(new Error(`Request Failed. Status Code: ${res.statusCode}`));
}
let body = '';
res.on('readable', () => {
body += res.read();
});
res.on('end', () => {
callback(null, body);
});
return undefined;
})
.on('error', e => {
callback(e);
});
return null;
}
} }
const pollyTTS = new PollyTTS(); const pollyTTS = new PollyTTS();

View file

@ -1,130 +1,103 @@
/* global settings,twitch,trovo, 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', 'user:read:follows', 'user:read:subscriptions', 'channel:read:redemptions']; const scopes = ['chat:edit', 'chat:read'];
const express = require('express'); const express = require('express');
const tempAuthServer = express(); let 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) {
console.log('1'); if (req.url !== '/auth') {
if (req.url !== '/auth') { let token = parseQueryString(req.query.auth);
console.log(req.url); settings.TWITCH.OAUTH_TOKEN = token['#access_token'];
const token = parseQueryString(req.query.auth); fs.writeFileSync(settingsPath, ini.stringify(settings));
console.log(req);
console.log(token);
settings.TWITCH.OAUTH_TOKEN = token['#access_token'];
fs.writeFileSync(settingsPath, JSON.stringify(settings));
resolve(token['#access_token']); resolve(token['#access_token']);
stopServer(); stopServer();
} else { }
res.send(htmlString); next();
} });
function stopServer() {
tempAuthServer.close();
}
const htmlString = `
<!DOCTYPE html>
<html>
<head>
<title>Authentication</title>
</head>
<body>
<h1>Authentication successful! You can close this window now.</h1>
<form name="auth" "action="auth" method="get" >
<input type="text" id="auth" name="auth"/>
</form>
</body>
</html>
<script>
function onSubmitComplete() {
close();
}
document.querySelector("#auth").style.display="none";
document.querySelector("#auth").value = document.location.hash;
document.auth.submit();
setTimeout(onSubmitComplete, 500);
</script>
`;
tempAuthServer.get('/auth', (req, res) => {
res.send(htmlString);
});
tempAuthServer.post('/auth', (req, res) => {
res.render('authentication', { name: req.body.name });
});
const server = http.createServer(tempAuthServer);
server.listen(port, () => {
const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent(
redirectUri,
)}&response_type=token&scope=${scopes.join(' ')}`;
shell.openExternal(authURL);
});
function stopServer() {
server.close(() => {});
}
}); });
const htmlString = ` function getTwitchUserId() {
<!DOCTYPE html> // Get user Logo with access token
<html> options = {
<head> method: 'GET',
<title>Authentication</title> url: `https://api.twitch.tv/helix/users`,
</head> headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` },
<body> };
<h1>Authentication successful! You can close this window now.</h1>
<form name="auth" "action="auth" method="get" >
<input type="text" id="auth" name="auth"/>
</form>
</body>
<script>
function onSubmitComplete() {
close();
}
document.querySelector("#auth").style.display="none";
document.querySelector("#auth").value = document.location.hash;
document.auth.submit();
setTimeout(onSubmitComplete, 500);
</script>
</html>
`;
console.log('-1');
const server = http.createServer(tempAuthServer);
server.listen(port, () => {
const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent(
redirectUri
)}&response_type=token&scope=${scopes.join('+')}`;
shell.openExternal(authURL);
});
function stopServer() {
server.close(() => {});
}
});
axios
.request(options)
.then((responseLogoUrl) => {
console.log(responseLogoUrl.data.data[0]);
settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name;
settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url;
settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id;
fs.writeFileSync(settingsPath, ini.stringify(settings));
})
.catch((error) => {
console.error(error);
});
}
function getTwitchOauthToken() { function getTwitchOauthToken() {
return twitchAuthentication().then(res => { return twitchAuthentication().then((res) => {
twitch.getTwitchUserId(); getTwitchUserId();
return res; return res;
}); });
} }
const trovoAuthentication = () => module.exports = { getTwitchOauthToken };
new Promise(resolve => {
const http = require('http');
const redirectUri = 'http://localhost:1989/auth';
const scopes = ['user_details_self', 'chat_send_self', 'send_to_my_channel', 'manage_messages'];
const express = require('express');
const tempAuthServer = express();
const port = 1989;
tempAuthServer.use(function (req, res) {
res.send(htmlString);
const token = req.query.access_token;
settings.TROVO.OAUTH_TOKEN = token;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
resolve(token);
stopServer();
});
const htmlString = `
<!DOCTYPE html>
<html>
<head>
<title>Authentication</title>
</head>
<body>
<h1>Authentication successful! You can close this window now.</h1>
</body>
</html>
`;
const server = http.createServer(tempAuthServer);
server.listen(port, () => {
const authURL = `https://open.trovo.live/page/login.html?client_id=${
settings.TROVO.CLIENT_ID
}&response_type=token&scope=${scopes.join('+')}&redirect_uri=${encodeURIComponent(redirectUri)}`;
const lol = shell.openExternal(authURL);
});
function stopServer() {
server.close(() => {});
}
});
function getTrovoOAuthToken() {
return trovoAuthentication().then(res => {
trovo.getTrovoUserId();
return res;
});
}
module.exports = { getTwitchOauthToken, getTrovoOAuthToken };

View file

@ -1,447 +1,167 @@
/* global settings, resourcesPath, sound, twitch, getLanguageProperties, addSingleTooltip, showChatMessage, languageObject, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const kill = require('kill-process-by-name'); var kill = require('kill-process-by-name');
let python; let python;
async function getInstalledVoices() { async function getInstalledVoices() {
if (!settings.TTS.USE_TTS) { if (!settings.TTS.USE_TTS) {
return; return;
}
addVoiceService('Internal');
try {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' });
if (response.ok) {
const responseData = await response.json();
console.log('Voices:', responseData);
internalVoices = responseData;
} else {
console.error('Failed to send termination signal to Flask server.');
} }
} catch (error) { addVoiceService('Internal');
console.error('Error sending termination signal:', error);
}
const primaryVoice = document.querySelector('#primaryVoice'); try {
const secondaryVoice = document.querySelector('#secondaryVoice'); const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' });
if (response.ok) {
function setVoicesinSelect(voiceSelect) { const responseData = await response.json();
const voices = Object.values(internalVoices.voices); console.log('Response:', responseData);
voices.forEach(voice => { internalVoices = responseData;
const option = document.createElement('option');
option.classList.add('option');
option.value = voice;
option.innerHTML = voice;
voiceSelect.appendChild(option);
});
}
setVoicesinSelect(primaryVoice);
primaryVoice.value = settings.TTS.PRIMARY_VOICE;
setVoicesinSelect(secondaryVoice);
secondaryVoice.value = settings.TTS.SECONDARY_VOICE;
}
// TODO: refactor
function setTranslatedUserMessage(message) {
const userMessage = document.getElementById(message.messageId);
const messageBox = userMessage.getElementsByClassName('msg-box')[0];
const languageElement = document.createElement('span');
languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis flag-icon user-flag`;
languageElement.setAttribute('tip', message.language.selectedLanguage.name);
userMessage.appendChild(languageElement);
addSingleTooltip(languageElement);
const translationHeader = document.createElement('div');
translationHeader.className = 'translation-header user';
translationHeader.innerText = 'Translation';
messageBox.appendChild(translationHeader);
const languageElement2 = document.createElement('span');
languageElement2.classList = `fi fi-${message.language.detectedLanguage.ISO3166} fis flag-icon user`;
languageElement2.setAttribute('tip', message.language.detectedLanguage.name);
addSingleTooltip(languageElement2);
messageBox.appendChild(languageElement2);
const translationMessage = document.createElement('div');
translationMessage.className = 'translation-message user';
translationMessage.innerText = message.translation;
messageBox.appendChild(translationMessage);
}
function setTranslatedMessage(message) {
// this determines if it is a message that is send by a user
const languageBox = document.getElementById(message.messageId).getElementsByClassName('language-icon flag-icon')[0];
// if (false) {
// twitch.sendMessage(
// `[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${settings.TWITCH.USERNAME}: ${message.translation}`
// );
// return setTranslatedUserMessage(message);
// }
if (message.language.selectedLanguage.ISO639 !== message.language.detectedLanguage.ISO639) {
const messageBox = document.getElementById(message.messageId).getElementsByClassName('msg-box')[0];
languageBox.classList = `fi fi-${message.language.detectedLanguage.ISO3166} fis language-icon flag-icon`;
languageBox.setAttribute('tip', message.language.detectedLanguage.name);
const translationHeader = document.createElement('div');
translationHeader.className = 'translation-header';
translationHeader.innerText = 'Translation';
messageBox.appendChild(translationHeader);
const translationIcon = document.createElement('div');
translationIcon.className = 'translation-icon';
const languageElement = document.createElement('span');
languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis flag-icon`;
languageElement.setAttribute('tip', message.language.selectedLanguage.name);
addSingleTooltip(languageElement);
translationIcon.appendChild(languageElement);
messageBox.appendChild(translationIcon);
const translationMessage = document.createElement('div');
translationMessage.className = 'translation-message';
translationMessage.innerText = message.translation;
messageBox.appendChild(translationMessage);
// showChatMessage();
const messages = document.body.querySelectorAll('.msg-container');
const lastMessage = messages[messages.length - 1];
lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' });
}
console.log(message);
if (settings.LANGUAGE.OUTPUT_TO_TTS) {
sound.playVoice({
originalMessage: message.originalMessage,
filteredMessage: message.translation,
logoUrl: message.logoUrl,
username: message.username,
formattedMessage: message.formattedMessage,
language: message.language
});
}
return message.language.detectedLanguage;
}
async function getTranslatedMessage(message) {
// TODO: translate primary language to
console.log(message);
console.log(message.isPrimaryLanguage ? message.language.selectedLanguage.IETF : message.language.detectedLanguage.IETF);
const requestOptions = {
method: 'POST', // HTTP method
headers: {
'Content-Type': 'application/json; charset="utf-8"' // Specify the content type
},
body: JSON.stringify({
message: message.message,
language: message.isPrimaryLanguage ? message.language.selectedLanguage.IETF : message.language.detectedLanguage.IETF
}) // Convert the data to JSON and include it in the request body
};
try {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/translate`, requestOptions);
const responseData = await response.json();
if (response.ok) {
console.log('Translated message:', responseData);
if (settings.LANGUAGE.BROADCAST_TRANSLATION) {
twitch.sendMessage(
`[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${message.username}: ${responseData.translation}`
);
}
setTranslatedMessage({
originalMessage: message.message,
translation: responseData.translation,
messageId: message.messageId,
language: message.language,
formattedMessage: message.formattedMessage,
username: message.username,
logoUrl: message.logoUrl
});
return message.language.detectedLanguage;
} else {
console.error(responseData);
if (responseData.code === 500) {
if (message.remainingDetectedLanguages.length > 0) {
message.language.detectedLanguage = getLanguageProperties(message.remainingDetectedLanguages[0]);
message.remainingDetectedLanguages.shift();
return getTranslatedMessage(message);
} else { } else {
message.message = 'Error, Could not translate message'; console.error('Failed to send termination signal to Flask server.');
message.language.detectedLanguage = getLanguageProperties('en-GB');
return getTranslatedMessage(message);
} }
} } catch (error) {
if (responseData.code === 429) { console.error('Error sending termination signal:', error);
message.language.detectedLanguage = getLanguageProperties('en-GB'); }
setTranslatedMessage({
originalMessage: message.message, let primaryVoice = document.querySelector('#primaryVoice');
translation: 'Rate limit exceeded, please change translation service.', let secondaryVoice = document.querySelector('#secondaryVoice');
messageId: message.messageId,
language: message.language, function setVoicesinSelect(voiceSelect) {
formattedMessage: message.formattedMessage, const voices = Object.values(internalVoices.voices);
username: message.username, voices.forEach((voice) => {
logoUrl: message.logoUrl const option = document.createElement('option');
option.classList.add('option');
option.value = voice;
option.innerHTML = voice;
voiceSelect.appendChild(option);
}); });
}
} }
} catch (error) { setVoicesinSelect(primaryVoice);
console.error('Error sending termination signal:', error); primaryVoice.value = settings.TTS.PRIMARY_VOICE;
message.language.detectedLanguage = getLanguageProperties('en-GB'); setVoicesinSelect(secondaryVoice);
setTranslatedMessage({ secondaryVoice.value = settings.TTS.SECONDARY_VOICE;
originalMessage: message.message,
translation: 'Error, could not translate message.',
messageId: message.messageId,
language: message.language,
formattedMessage: message.formattedMessage,
username: message.username,
logoUrl: message.logoUrl
});
}
}
async function filterLanguage(message) {
const selectedPrimaryLanguage = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO);
const selectedPrimaryLanguageIndex =
message.languages.indexOf(selectedPrimaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedPrimaryLanguage.ISO639);
const selectedSecondaryLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE);
const selectedSecondaryLanguageIndex =
message.languages.indexOf(selectedSecondaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedSecondaryLanguage.ISO639);
let detectedLanguage = '';
const remainingDetectedLanguages = [];
const detectedLanguages = message.languages.slice();
for (const [index, language] of detectedLanguages.entries()) {
detectedLanguage = getLanguageProperties(language);
if (detectedLanguage !== 'error') {
detectedLanguages.splice(index, 1);
break;
}
}
for (const [index, language] of detectedLanguages.entries()) {
const remainderLanguage = getLanguageProperties(language);
if (remainderLanguage !== 'error') {
remainingDetectedLanguages.push(remainderLanguage.IETF);
}
}
const language = selectedPrimaryLanguageIndex < selectedSecondaryLanguageIndex ? selectedPrimaryLanguage : detectedLanguage;
if (settings.LANGUAGE.TRANSLATE_TO !== 'none' && selectedPrimaryLanguage.ISO639 !== language.ISO639) {
// console.log('1');
console.log('hola');
getTranslatedMessage({
message: message.message,
messageId: message.messageId,
remainingDetectedLanguages,
isPrimaryLanguage: false,
language: {
selectedLanguage: selectedPrimaryLanguage,
detectedLanguage: detectedLanguage
},
username: message.username,
formattedMessage: message.formattedMessage,
logoUrl: message.logoUrl
});
} else {
console.log('adios');
// console.log('2');
setTranslatedMessage({
originalMessage: message.message,
translation: message.message,
messageId: message.messageId,
language: {
selectedLanguage: selectedPrimaryLanguage,
detectedLanguage: selectedPrimaryLanguage
},
formattedMessage: message.formattedMessage,
username: message.username,
logoUrl: message.logoUrl
});
// getTranslatedMessage({
// message: message.message,
// messageId: message.messageId,
// remainingDetectedLanguages: [],
// isPrimaryLanguage: true,
// language: {
// selectedLanguage: selectedSecondaryLanguage,
// detectedLanguage: selectedPrimaryLanguage
// },
// username: message.username,
// formattedMessage: message.formattedMessage,
// logoUrl: message.logoUrl
// });
}
return language;
}
async function getDetectedLanguage(message) {
if (!settings.LANGUAGE.USE_DETECTION) {
return;
}
const requestOptions = {
method: 'POST', // HTTP method
headers: {
'Content-Type': 'application/json' // Specify the content type
},
body: JSON.stringify({ message: message.message }) // Convert the data to JSON and include it in the request body
};
try {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/detect`, requestOptions);
if (response.ok) {
const responseData = await response.json();
console.log('Detected Languages:', responseData);
return await filterLanguage({
languages: responseData.languages,
message: message.message,
messageId: message.messageId,
username: message.username,
formattedMessage: message.formattedMessage
});
} else {
console.error('Failed to send termination signal to Flask server.');
}
} catch (error) {
console.error('Error sending termination signal:', error);
}
} }
async function getBackendServerStatus() { async function getBackendServerStatus() {
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) {
const responseData = await response.json(); const responseData = await response.json();
console.log('Status:', responseData); console.log('Response:', responseData);
} else { } else {
console.error('Failed to send termination signal to Flask server.'); console.error('Failed to send termination signal to Flask server.');
}
} catch (error) {
console.error('Error sending termination signal:', error);
} }
} catch (error) {
console.error('Error sending termination signal:', error);
}
} }
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();
}); });
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
eventSource.close(); eventSource.close();
}); });
} }
async function getInternalTTSAudio(requestData) { async function getInternalTTSAudio(requestData) {
ttsRequestCount++; ttsRequestCount++;
requestData.count = ttsRequestCount; requestData.count = ttsRequestCount;
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 {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions); const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions);
if (response.ok) { if (response.ok) {
const responseData = await response.json(); const responseData = await response.json();
console.log('Audio:', responseData); console.log('Response:', responseData);
return ttsRequestCount; return ttsRequestCount;
} else { } else {
console.error('Failed to send termination signal to Flask server.'); console.error('Failed to send termination signal to Flask server.');
}
} catch (error) {
console.error('Error sending termination signal:', error);
} }
} catch (error) {
console.error('Error sending termination signal:', error);
}
} }
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(resourcesPath, '../backend/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}`);
}); });
// 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}`); console.error(`${data}`);
if (data.toString().startsWith('INFO:waitress:Serving on')) { resolve('finished'); // cannot get it to resolve with stdout
resolve('finished'); });
} else {
console.error(`${data}`);
}
});
// 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}`);
} }
}); });
async function initiateBackend() { async function initiateBackend() {
try { try {
createBackendServer().then(() => { createBackendServer().then(() => {
getBackendServerStatus(); getBackendServerStatus();
getInstalledVoices(); getInstalledVoices();
if (settings.STT.USE_STT && !settings.STT.LANGUAGE === '') { if (settings.STT.USE_STT) {
startSTT(); startSTT();
} }
}); });
} catch (error) { } catch (error) {
console.error('Error during backend initialization:', error); console.error('Error during backend initialization:', error);
} }
} }
initiateBackend(); initiateBackend();
// TODO: convert to restartServer function //TODO: convert to restartServer function
ipcRenderer.on('quit-event', async () => { ipcRenderer.on('quit-event', async () => {
try { try {
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' }); const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' });
if (response.ok) { if (response.ok) {
const responseData = await response.json(); const responseData = await response.json();
console.log('Response:', responseData); console.log('Response:', responseData);
kill('loquendoBot_backend'); kill('loquendoBot_backend');
} else { } else {
console.error('Failed to send termination signal to Flask server.'); console.error('Failed to send termination signal to Flask server.');
kill('loquendoBot_backend'); kill('loquendoBot_backend');
}
} catch (error) {
console.error('Error sending termination signal:', error);
kill('loquendoBot_backend');
} }
} catch (error) {
console.error('Error sending termination signal:', error);
kill('loquendoBot_backend');
}
}); });
module.exports = { getInternalTTSAudio, getDetectedLanguage, getTranslatedMessage }; module.exports = { getInternalTTSAudio };

View file

@ -1,94 +1,55 @@
/* global messageTemplates,getLanguageProperties, backend, messageId emojiPicker, settings, getPostTime, showChatMessage, twitch */ function getResponse() {
const userText = document.querySelector('#textInput').value;
async function getResponse() { // If nothing is written don't do anything
const userText = document.querySelector('#textInput').value; if (userText === '') {
return;
}
// If nothing is written don't do anything // Create chat message from received data
if (userText === '') { const article = document.createElement('article');
return; article.className = 'msg-container-user';
}
messageId++; article.innerHTML = messageTemplates.userTemplate;
// Create chat message from received data const userImg = article.querySelector('.icon-container-user > .user-img-user');
const article = document.createElement('article'); if (userImg) {
article.setAttribute('id', messageId); userImg.src = settings.TWITCH.USER_LOGO_URL;
article.className = 'msg-container user'; }
article.innerHTML = messageTemplates.userTemplate; const postTime = article.querySelector('.post-time-user');
const userImg = article.querySelector('.user-img'); const iconContainer = article.querySelector('.icon-container-user');
if (userImg) { iconContainer.appendChild(postTime);
userImg.src = settings.TWITCH.USER_LOGO_URL;
}
const postTime = article.querySelector('.post-time'); if (postTime) {
postTime.innerText = getPostTime();
}
if (postTime) { const msg = article.querySelector('.msg-box-user');
postTime.innerText = getPostTime(); if (msg) {
} msg.innerText = userText;
}
article.appendChild(postTime); // Appends the message to the main chat box (shows the message)
showChatMessage(article, true);
const msg = article.querySelector('.msg-box'); twitch.sendMessage(userText);
if (msg) {
await replaceChatMessageWithCustomEmojis(userText).then(data => {
msg.innerHTML = data;
// Appends the message to the main chat box (shows the message) // Empty input box after sending message
showChatMessage(article); document.body.querySelector('#textInput').value = '';
twitch.sendMessage(userText);
if (settings.LANGUAGE.SEND_TRANSLATION) {
const selectedLanguage = getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_IN);
const detectedLanguage = getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_OUT);
backend.getTranslatedMessage({
message: data,
messageId: messageId,
remainingDetectedLanguages: [],
language: {
selectedLanguage,
detectedLanguage
},
formattedMessage: data,
username: 'You',
logoUrl: settings.TWITCH.USER_LOGO_URL
});
}
// Empty input box after sending message
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 class="emote" 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();
} }
}); });
// Function that will execute when you click the 'send' button // Function that will execute when you click the 'send' button
document.body.querySelector('#SendButton').addEventListener('click', () => { document.body.querySelector('#SendButton').addEventListener('click', () => {
getResponse(); getResponse();
}); });
// #endregion // #endregion
@ -97,21 +58,21 @@ document.body.querySelector('#SendButton').addEventListener('click', () => {
// Left panel // Left panel
document.body.querySelector('.circle-left').addEventListener('click', () => { document.body.querySelector('.circle-left').addEventListener('click', () => {
const menu = document.body.querySelector('.sidepanel-left'); const menu = document.body.querySelector('.sidepanel-left');
if (menu.classList.contains('collapse-menu-left')) { if (menu.classList.contains('collapse-menu-left')) {
menu.classList.remove('collapse-menu-left'); menu.classList.remove('collapse-menu-left');
} else { } else {
menu.classList.add('collapse-menu-left'); menu.classList.add('collapse-menu-left');
} }
const leftCircle = document.body.querySelector('.circle-left'); const leftCircle = document.body.querySelector('.circle-left');
if (leftCircle.classList.contains('collapse-circle-left')) { if (leftCircle.classList.contains('collapse-circle-left')) {
leftCircle.classList.remove('collapse-circle-left'); leftCircle.classList.remove('collapse-circle-left');
} else { } else {
leftCircle.classList.add('collapse-circle-left'); leftCircle.classList.add('collapse-circle-left');
} }
}); });
// #region Show panels // #region Show panels
@ -120,67 +81,65 @@ document.body.querySelector('.circle-left').addEventListener('click', () => {
// TODO : optimize show panels // TODO : optimize show panels
// Function that shows and hides the option panels. (TTS, Configuration, Commands) // Function that shows and hides the option panels. (TTS, Configuration, Commands)
const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => { const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
const btn = document.querySelector(btnSelectorID); const btn = document.querySelector(btnSelectorID);
const panel = document.querySelector(panelSelectorID); const panel = document.querySelector(panelSelectorID);
const panels = document.querySelectorAll(panelSelectorClass); const panels = document.querySelectorAll(panelSelectorClass);
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')) {
panel.classList.add('show'); } else {
} panel.classList.add('show');
}, }
{ },
capture: true {
} capture: true,
); },
);
}; };
displayPanel('.OptionPanel', '#Configuration', '#btnConfiguration'); displayPanel('.OptionPanel', '#Configuration', '#btnConfiguration');
displayPanel('.OptionPanel', '#Logs', '#btnLogs'); displayPanel('.OptionPanel', '#Logs', '#btnLogs');
displayPanel('.OptionPanel', '#BrowsersourceChatBubble', '#btnBrowsersourceChatBubble'); displayPanel('.OptionPanel', '#BrowsersourceChat', '#btnBrowsersourceChat');
displayPanel('.OptionPanel', '#BrowsersourceVtuber', '#btnBrowsersourceVtuber'); displayPanel('.OptionPanel', '#BrowsersourceVtuber', '#btnBrowsersourceVtuber');
displayPanel('.OptionPanel', '#BrowsersourcePNGTuber', '#btnBrowsersourcePNGTuber');
displayPanel('.OptionPanel', '#FaceMask', '#btnFaceMask');
displayPanel('.OptionPanel', '#Chat', '#btnChat'); displayPanel('.OptionPanel', '#Chat', '#btnChat');
displayPanel('.OptionPanel', '#ThemeCreator', '#btnThemeCreator'); displayPanel('.OptionPanel', '#ThemeCreator', '#btnThemeCreator');
displayPanel('.OptionPanel', '#ChatCreator', '#btnChatCreator'); displayPanel('.OptionPanel', '#ChatCreator', '#btnChatCreator');
// #endregion // #endregion
const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => { const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
const btn = document.querySelector(btnSelectorID); const btn = document.querySelector(btnSelectorID);
const panel = document.querySelector(panelSelectorID); const panel = document.querySelector(panelSelectorID);
const panels = document.querySelectorAll(panelSelectorClass); const panels = document.querySelectorAll(panelSelectorClass);
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')) {
panel.classList.add('item-active'); } else {
} panel.classList.add('item-active');
}, }
{ },
capture: true {
} capture: true,
); },
);
}; };
displayPanelX('.item', '#btnChat', '#btnChat'); displayPanelX('.item', '#btnChat', '#btnChat');
displayPanelX('.item', '#btnBrowsersourceChatBubble', '#btnBrowsersourceChatBubble'); displayPanelX('.item', '#btnBrowsersourceChat', '#btnBrowsersourceChat');
displayPanelX('.item', '#btnBrowsersourceVtuber', '#btnBrowsersourceVtuber'); displayPanelX('.item', '#btnBrowsersourceVtuber', '#btnBrowsersourceVtuber');
displayPanelX('.item', '#btnBrowsersourcePNGTuber', '#btnBrowsersourcePNGTuber');
displayPanelX('.item', '#btnFaceMask', '#btnFaceMask');
displayPanelX('.item', '#btnLogs', '#btnLogs'); displayPanelX('.item', '#btnLogs', '#btnLogs');
displayPanelX('.item', '#btnConfiguration', '#btnConfiguration'); displayPanelX('.item', '#btnConfiguration', '#btnConfiguration');
displayPanelX('.item', '#btnThemeCreator', '#btnThemeCreator'); displayPanelX('.item', '#btnThemeCreator', '#btnThemeCreator');
@ -189,7 +148,3 @@ displayPanelX('.item', '#btnChatCreator', '#btnChatCreator');
// #region Show/Hide Theme Creator // #region Show/Hide Theme Creator
// #endregion // #endregion
module.exports = {
replaceChatMessageWithCustomEmojis
};

View file

@ -1,102 +0,0 @@
/* global settings, bot,settingsPath, fs, ini, messageId, showChatMessage, messageTemplates, chat, addSingleTooltip,getPostTime */
const Dlive = require('dlivetv-api');
const bot = new Dlive(settings.DLIVE.API_KEY);
function setTrovoSendButton() {
const languageSelectContent = document.querySelector('.send-to-channel');
const option = document.createElement('div');
option.classList = 'language-select';
const checkbox = document.createElement('input');
checkbox.classList = 'checkbox';
checkbox.type = 'checkbox';
checkbox.id = 'SEND_CHAT_DLIVE';
option.appendChild(checkbox);
const label = document.createElement('label');
label.classList = 'toggle-small';
option.setAttribute('for', 'SEND_CHAT_DLIVE');
checkbox.checked = settings.TROVO.SEND_CHAT;
option.appendChild(label);
const network = document.createElement('img');
network.src = './images/dlive.png';
network.classList = 'emote';
option.appendChild(network);
option.addEventListener('click', () => {
checkbox.checked = !checkbox.checked;
settings.TROVO.SEND_CHAT = checkbox.checked;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
});
languageSelectContent.appendChild(option);
}
setTrovoSendButton();
bot.on('ChatText', message => {
// console.log(`[${msg.sender.displayname}]: ${msg.content}`);
displayDLiveMessage(message);
});
// bot.sendMessage('test').then(console.log).catch(console.log);
bot.on('ChatGift', msg => {
console.log(`${msg.sender.displayname} donated ${msg.amount} ${msg.gift}'s`);
});
bot.on('ChatHost', msg => {
console.log(`${msg.sender.displayname} coming in with that ${msg.viewer} viewer host!`);
});
bot.on('ChatFollow', msg => {
console.log(`New follower! ${msg.sender.displayname} has joined the party!`);
});
async function displayDLiveMessage(message) {
messageId++;
const article = document.createElement('article');
article.className = 'msg-container sender';
article.setAttribute('id', messageId);
article.innerHTML = messageTemplates.dliveTemplate;
const userImg = article.querySelector('.user-img');
if (userImg) {
userImg.src = message.sender.avatar;
userImg.setAttribute('tip', '');
}
addSingleTooltip(userImg);
const usernameHtml = article.querySelector('.username');
if (usernameHtml) {
usernameHtml.innerText = message.sender.displayname;
}
const postTime = article.querySelector('.post-time');
if (postTime) {
postTime.innerText = getPostTime();
}
article.appendChild(postTime);
const formattedMessage = article.querySelector('.msg-box');
if (formattedMessage) {
formattedMessage.innerHTML = message.content;
// message.message.forEach(entry => {
// if (entry.text) {
// formattedMessage.innerHTML += entry.text;
// } else {
// formattedMessage.innerHTML += `<img src="${entry.url}"/>`;
// }
// });
}
await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => {
formattedMessage.innerHTML = data;
showChatMessage(article);
});
}

View file

@ -1,33 +1,31 @@
/* global settings, addVoiceService, googleVoices */
function getGoogleVoices() { function getGoogleVoices() {
if (!settings.GOOGLE.USE_GOOGLE) { if (!settings.GOOGLE.USE_GOOGLE) {
return; return;
} }
addVoiceService('Google'); addVoiceService('Google');
const primaryVoice = document.querySelector('#primaryGoogleVoice'); let primaryVoice = document.querySelector('#primaryGoogleVoice');
const secondaryVoice = document.querySelector('#secondaryGoogleVoice'); let 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');
option.value = voice; option.value = voice;
option.innerHTML = voice; option.innerHTML = voice;
voiceSelect.appendChild(option); voiceSelect.appendChild(option);
}); });
} }
setVoicesinSelect(primaryVoice); setVoicesinSelect(primaryVoice);
primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE; primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE;
setVoicesinSelect(secondaryVoice); setVoicesinSelect(secondaryVoice);
secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE; secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE;
} }
if (settings.GOOGLE.USE_GOOGLE) { if (settings.GOOGLE.USE_GOOGLE) {
getGoogleVoices(); getGoogleVoices();
} }

View file

@ -1,334 +1,331 @@
// TODO: Enable STT: // TODO: Enable STT:
// Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns) // Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns)
// *automatic translation: make an translation.js and add ALL the texts and have it translated if user chooses a language in top bar // *automatic translation: make an translation.js and add ALL the texts and have it translated if user chooses a language in top bar
// *info page with credits, version and more info // *info page with credits, version and more info
const languages = { const languages = {
none: { IETF: 'none', ISO639: 'none', ISO3166: 'xx' }, acehnese: { IETF: 'ace-ID', 'ISO-639': 'ace' },
english: { IETF: 'en-GB', ISO639: 'en', ISO3166: 'gb' }, afrikaans: { IETF: 'af-ZA', 'ISO-639': 'af' },
spanish: { IETF: 'es-ES', ISO639: 'es', ISO3166: 'es' }, akan: { IETF: 'ak-GH', 'ISO-639': 'ak' },
dutch: { IETF: 'nl-NL', ISO639: 'nl', ISO3166: 'nl' }, albanian: { IETF: 'sq-AL', 'ISO-639': 'sq' },
'chinese simplified': { IETF: 'zh-CN', ISO639: 'zh', ISO3166: 'cn' }, amharic: { IETF: 'am-ET', 'ISO-639': 'am' },
russian: { IETF: 'ru-RU', ISO639: 'ru', ISO3166: 'ru' }, 'antigua and barbuda creole english': { IETF: 'aig-AG', 'ISO-639': 'aig' },
indonesian: { IETF: 'id-ID', ISO639: 'id', ISO3166: 'id' }, arabic: { IETF: 'ar-SA', 'ISO-639': 'ar' },
hindi: { IETF: 'hi-IN', ISO639: 'hi', ISO3166: 'in' }, 'arabic egyptian': { IETF: 'ar-EG', 'ISO-639': 'ar' },
filipino: { IETF: 'fil-PH', ISO639: 'fil', ISO3166: 'ph' }, aragonese: { IETF: 'an-ES', 'ISO-639': 'an' },
turkish: { IETF: 'tr-TR', ISO639: 'tr', ISO3166: 'tr' }, armenian: { IETF: 'hy-AM', 'ISO-639': 'hy' },
acehnese: { IETF: 'ace-ID', ISO639: 'ace', ISO3166: 'id' }, assamese: { IETF: 'as-IN', 'ISO-639': 'as' },
afrikaans: { IETF: 'af-ZA', ISO639: 'af', ISO3166: 'za' }, asturian: { IETF: 'ast-ES', 'ISO-639': 'ast' },
akan: { IETF: 'ak-GH', ISO639: 'ak', ISO3166: 'gh' }, 'austrian german': { IETF: 'de-AT', 'ISO-639': 'de' },
albanian: { IETF: 'sq-AL', ISO639: 'sq', ISO3166: 'al' }, awadhi: { IETF: 'awa-IN', 'ISO-639': 'awa' },
amharic: { IETF: 'am-ET', ISO639: 'am', ISO3166: 'et' }, 'ayacucho quechua': { IETF: 'quy-PE', 'ISO-639': 'quy' },
'antigua and barbuda creole english': { IETF: 'aig-AG', ISO639: 'aig', ISO3166: 'ag' }, azerbaijani: { IETF: 'az-AZ', 'ISO-639': 'az' },
arabic: { IETF: 'ar-SA', ISO639: 'ar', ISO3166: 'sa' }, 'bahamas creole english': { IETF: 'bah-BS', 'ISO-639': 'bah' },
'arabic egyptian': { IETF: 'ar-EG', ISO639: 'arz', ISO3166: 'eg' }, bajan: { IETF: 'bjs-BB', 'ISO-639': 'bjs' },
aragonese: { IETF: 'es-ES', ISO639: 'an', ISO3166: 'es' }, balinese: { IETF: 'ban-ID', 'ISO-639': 'ban' },
armenian: { IETF: 'hy-AM', ISO639: 'hy', ISO3166: 'am' }, 'balkan gipsy': { IETF: 'rm-RO', 'ISO-639': 'rm' },
assamese: { IETF: 'as-IN', ISO639: 'as', ISO3166: 'in' }, bambara: { IETF: 'bm-ML', 'ISO-639': 'bm' },
asturian: { IETF: 'ast-ES', ISO639: 'ast', ISO3166: 'es' }, banjar: { IETF: 'bjn-ID', 'ISO-639': 'bjn' },
'austrian german': { IETF: 'de-AT', ISO639: 'de', ISO3166: 'at' }, bashkir: { IETF: 'ba-RU', 'ISO-639': 'ba' },
awadhi: { IETF: 'awa-IN', ISO639: 'awa', ISO3166: 'in' }, basque: { IETF: 'eu-ES', 'ISO-639': 'eu' },
'ayacucho quechua': { IETF: 'quy-PE', ISO639: 'quy', ISO3166: 'pe' }, belarusian: { IETF: 'be-BY', 'ISO-639': 'be' },
azerbaijani: { IETF: 'az-AZ', ISO639: 'az', ISO3166: 'az' }, 'belgian french': { IETF: 'fr-BE', 'ISO-639': 'fr' },
'bahamas creole english': { IETF: 'bah-BS', ISO639: 'bah', ISO3166: 'bs' }, bemba: { IETF: 'bem-ZM', 'ISO-639': 'bem' },
bajan: { IETF: 'bjs-BB', ISO639: 'bjs', ISO3166: 'bb' }, bengali: { IETF: 'bn-IN', 'ISO-639': 'bn' },
balinese: { IETF: 'ban-ID', ISO639: 'ban', ISO3166: 'id' }, bhojpuri: { IETF: 'bho-IN', 'ISO-639': 'bho' },
'balkan gipsy': { IETF: 'rm-RO', ISO639: 'rm', ISO3166: 'ro' }, bihari: { IETF: 'bh-IN', 'ISO-639': 'bh' },
bambara: { IETF: 'bm-ML', ISO639: 'bm', ISO3166: 'ml' }, bislama: { IETF: 'bi-VU', 'ISO-639': 'bi' },
banjar: { IETF: 'bjn-ID', ISO639: 'bjn', ISO3166: 'id' }, borana: { IETF: 'gax-KE', 'ISO-639': 'gax' },
bashkir: { IETF: 'ba-RU', ISO639: 'ba', ISO3166: 'ru' }, bosnian: { IETF: 'bs-BA', 'ISO-639': 'bs' },
basque: { IETF: 'eu-ES', ISO639: 'eu', ISO3166: 'es-pv' }, 'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', 'ISO-639': 'bs' },
belarusian: { IETF: 'be-BY', ISO639: 'be', ISO3166: 'by' }, breton: { IETF: 'br-FR', 'ISO-639': 'br' },
'belgian french': { IETF: 'fr-BE', ISO639: 'fr', ISO3166: 'be' }, buginese: { IETF: 'bug-ID', 'ISO-639': 'bug' },
bemba: { IETF: 'bem-ZM', ISO639: 'bem', ISO3166: 'zm' }, bulgarian: { IETF: 'bg-BG', 'ISO-639': 'bg' },
bengali: { IETF: 'bn-IN', ISO639: 'bn', ISO3166: 'bd' }, burmese: { IETF: 'my-MM', 'ISO-639': 'my' },
bhojpuri: { IETF: 'bho-IN', ISO639: 'bho', ISO3166: 'in' }, catalan: { IETF: 'ca-ES', 'ISO-639': 'ca' },
bihari: { IETF: 'bh-IN', ISO639: 'bh', ISO3166: 'in' }, 'catalan valencian': { IETF: 'cav-ES', 'ISO-639': 'cav' },
bislama: { IETF: 'bi-VU', ISO639: 'bi', ISO3166: 'vu' }, cebuano: { IETF: 'ceb-PH', 'ISO-639': 'ceb' },
borana: { IETF: 'gax-KE', ISO639: 'gax', ISO3166: 'ke' }, 'central atlas tamazight': { IETF: 'tzm-MA', 'ISO-639': 'tzm' },
bosnian: { IETF: 'bs-BA', ISO639: 'bs', ISO3166: 'ba' }, 'central aymara': { IETF: 'ayr-BO', 'ISO-639': 'ayr' },
'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', ISO639: 'bs', ISO3166: 'ba' }, 'central kanuri (latin script)': { IETF: 'knc-NG', 'ISO-639': 'knc' },
breton: { IETF: 'br-FR', ISO639: 'br', ISO3166: 'fr' }, 'chadian arabic': { IETF: 'shu-TD', 'ISO-639': 'shu' },
buginese: { IETF: 'bug-ID', ISO639: 'bug', ISO3166: 'id' }, chamorro: { IETF: 'ch-GU', 'ISO-639': 'ch' },
bulgarian: { IETF: 'bg-BG', ISO639: 'bg', ISO3166: 'bg' }, cherokee: { IETF: 'chr-US', 'ISO-639': 'chr' },
burmese: { IETF: 'my-MM', ISO639: 'my', ISO3166: 'mm' }, chhattisgarhi: { IETF: 'hne-IN', 'ISO-639': 'hne' },
catalan: { IETF: 'ca-ES', ISO639: 'ca', ISO3166: 'es' }, 'chinese simplified': { IETF: 'zh-CN', 'ISO-639': 'zh' },
'catalan valencian': { IETF: 'cav-ES', ISO639: 'cav', ISO3166: 'es' }, 'chinese trad. (hong kong)': { IETF: 'zh-HK', 'ISO-639': 'zh' },
cebuano: { IETF: 'ceb-PH', ISO639: 'ceb', ISO3166: 'ph' }, 'chinese traditional': { IETF: 'zh-TW', 'ISO-639': 'zh' },
'central atlas tamazight': { IETF: 'tzm-MA', ISO639: 'tzm', ISO3166: 'ma' }, 'chinese traditional macau': { IETF: 'zh-MO', 'ISO-639': 'zh' },
'central aymara': { IETF: 'ayr-BO', ISO639: 'ayr', ISO3166: 'bo' }, chittagonian: { IETF: 'ctg-BD', 'ISO-639': 'ctg' },
'central kanuri (latin script)': { IETF: 'knc-NG', ISO639: 'knc', ISO3166: 'ng' }, chokwe: { IETF: 'cjk-AO', 'ISO-639': 'cjk' },
'chadian arabic': { IETF: 'shu-TD', ISO639: 'shu', ISO3166: 'td' }, 'classical greek': { IETF: 'grc-GR', 'ISO-639': 'grc' },
chamorro: { IETF: 'ch-GU', ISO639: 'ch', ISO3166: 'gu' }, 'comorian ngazidja': { IETF: 'zdj-KM', 'ISO-639': 'zdj' },
cherokee: { IETF: 'chr-US', ISO639: 'chr', ISO3166: 'us' }, coptic: { IETF: 'cop-EG', 'ISO-639': 'cop' },
chhattisgarhi: { IETF: 'hne-IN', ISO639: 'hne', ISO3166: 'in' }, 'crimean tatar': { IETF: 'crh-RU', 'ISO-639': 'crh' },
'chinese trad. (hong kong)': { IETF: 'zh-HK', ISO639: 'zh', ISO3166: 'hk' }, 'crioulo upper guinea': { IETF: 'pov-GW', 'ISO-639': 'pov' },
'chinese traditional': { IETF: 'zh-TW', ISO639: 'zh', ISO3166: 'tw' }, croatian: { IETF: 'hr-HR', 'ISO-639': 'hr' },
'chinese traditional macau': { IETF: 'zh-MO', ISO639: 'zh', ISO3166: 'mo' }, czech: { IETF: 'cs-CZ', 'ISO-639': 'cs' },
chittagonian: { IETF: 'ctg-BD', ISO639: 'ctg', ISO3166: 'bd' }, danish: { IETF: 'da-DK', 'ISO-639': 'da' },
chokwe: { IETF: 'cjk-AO', ISO639: 'cjk', ISO3166: 'ao' }, dari: { IETF: 'prs-AF', 'ISO-639': 'prs' },
'classical greek': { IETF: 'grc-GR', ISO639: 'grc', ISO3166: 'gr' }, dimli: { IETF: 'diq-TR', 'ISO-639': 'diq' },
'comorian ngazidja': { IETF: 'zdj-KM', ISO639: 'zdj', ISO3166: 'km' }, dutch: { IETF: 'nl-NL', 'ISO-639': 'nl' },
coptic: { IETF: 'cop-EG', ISO639: 'cop', ISO3166: 'eg' }, dyula: { IETF: 'dyu-CI', 'ISO-639': 'dyu' },
'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' }, dzongkha: { IETF: 'dz-BT', 'ISO-639': 'dz' },
'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' }, 'eastern yiddish': { IETF: 'ydd-US', 'ISO-639': 'ydd' },
croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' }, emakhuwa: { IETF: 'vmw-MZ', 'ISO-639': 'vmw' },
'serbo-croatian': { IETF: 'sr-Cyrl-RS', ISO639: 'sh', ISO3166: 'sr' }, english: { IETF: 'en-GB', 'ISO-639': 'en' },
czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' }, 'english australia': { IETF: 'en-AU', 'ISO-639': 'en' },
danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' }, 'english canada': { IETF: 'en-CA', 'ISO-639': 'en' },
dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' }, 'english india': { IETF: 'en-IN', 'ISO-639': 'en' },
dimli: { IETF: 'diq-TR', ISO639: 'diq', ISO3166: 'tr' }, 'english ireland': { IETF: 'en-IE', 'ISO-639': 'en' },
dyula: { IETF: 'dyu-CI', ISO639: 'dyu', ISO3166: 'ci' }, 'english new zealand': { IETF: 'en-NZ', 'ISO-639': 'en' },
dzongkha: { IETF: 'dz-BT', ISO639: 'dz', ISO3166: 'bt' }, 'english singapore': { IETF: 'en-SG', 'ISO-639': 'en' },
'eastern yiddish': { IETF: 'ydd-US', ISO639: 'ydd', ISO3166: 'il' }, 'english south africa': { IETF: 'en-ZA', 'ISO-639': 'en' },
emakhuwa: { IETF: 'vmw-MZ', ISO639: 'vmw', ISO3166: 'mz' }, 'english us': { IETF: 'en-US', 'ISO-639': 'en' },
'english australia': { IETF: 'en-AU', ISO639: 'en', ISO3166: 'au' }, esperanto: { IETF: 'eo-EU', 'ISO-639': 'eo' },
'english canada': { IETF: 'en-CA', ISO639: 'en', ISO3166: 'ca' }, estonian: { IETF: 'et-EE', 'ISO-639': 'et' },
'english india': { IETF: 'en-IN', ISO639: 'en', ISO3166: 'in' }, ewe: { IETF: 'ee-GH', 'ISO-639': 'ee' },
'english ireland': { IETF: 'en-IE', ISO639: 'en', ISO3166: 'ie' }, fanagalo: { IETF: 'fn-FNG', 'ISO-639': 'fn' },
'english new zealand': { IETF: 'en-NZ', ISO639: 'en', ISO3166: 'nz' }, faroese: { IETF: 'fo-FO', 'ISO-639': 'fo' },
'english singapore': { IETF: 'en-SG', ISO639: 'en', ISO3166: 'sg' }, fijian: { IETF: 'fj-FJ', 'ISO-639': 'fj' },
'english south africa': { IETF: 'en-ZA', ISO639: 'en', ISO3166: 'za' }, filipino: { IETF: 'fil-PH', 'ISO-639': 'fil' },
'english us': { IETF: 'en-US', ISO639: 'en', ISO3166: 'us' }, finnish: { IETF: 'fi-FI', 'ISO-639': 'fi' },
esperanto: { IETF: 'eo-EU', ISO639: 'eo', ISO3166: 'eu' }, flemish: { IETF: 'nl-BE', 'ISO-639': 'nl' },
estonian: { IETF: 'et-EE', ISO639: 'et', ISO3166: 'ee' }, fon: { IETF: 'fon-BJ', 'ISO-639': 'fon' },
ewe: { IETF: 'ee-GH', ISO639: 'ee', ISO3166: 'gh' }, french: { IETF: 'fr-FR', 'ISO-639': 'fr' },
fanagalo: { IETF: 'fn-FNG', ISO639: 'fn', ISO3166: 'za' }, 'french canada': { IETF: 'fr-CA', 'ISO-639': 'fr' },
faroese: { IETF: 'fo-FO', ISO639: 'fo', ISO3166: 'fo' }, 'french swiss': { IETF: 'fr-CH', 'ISO-639': 'fr' },
fijian: { IETF: 'fj-FJ', ISO639: 'fj', ISO3166: 'fj' }, friulian: { IETF: 'fur-IT', 'ISO-639': 'fur' },
finnish: { IETF: 'fi-FI', ISO639: 'fi', ISO3166: 'fi' }, fula: { IETF: 'ff-FUL', 'ISO-639': 'ff' },
flemish: { IETF: 'nl-BE', ISO639: 'nl', ISO3166: 'be' }, galician: { IETF: 'gl-ES', 'ISO-639': 'gl' },
fon: { IETF: 'fon-BJ', ISO639: 'fon', ISO3166: 'bj' }, gamargu: { IETF: 'mfi-NG', 'ISO-639': 'mfi' },
french: { IETF: 'fr-FR', ISO639: 'fr', ISO3166: 'fr' }, garo: { IETF: 'grt-IN', 'ISO-639': 'grt' },
'french canada': { IETF: 'fr-CA', ISO639: 'fr', ISO3166: 'ca' }, georgian: { IETF: 'ka-GE', 'ISO-639': 'ka' },
'french swiss': { IETF: 'fr-CH', ISO639: 'fr', ISO3166: 'ch' }, german: { IETF: 'de-DE', 'ISO-639': 'de' },
friulian: { IETF: 'fur-IT', ISO639: 'fur', ISO3166: 'it' }, gilbertese: { IETF: 'gil-KI', 'ISO-639': 'gil' },
fula: { IETF: 'ff-FUL', ISO639: 'ff', ISO3166: 'cm' }, glavda: { IETF: 'glw-NG', 'ISO-639': 'glw' },
galician: { IETF: 'gl-ES', ISO639: 'gl', ISO3166: 'es-ga' }, greek: { IETF: 'el-GR', 'ISO-639': 'el' },
gamargu: { IETF: 'mfi-NG', ISO639: 'mfi', ISO3166: 'ng' }, 'grenadian creole english': { IETF: 'gcl-GD', 'ISO-639': 'gcl' },
garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' }, guarani: { IETF: 'gn-PY', 'ISO-639': 'gn' },
georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' }, gujarati: { IETF: 'gu-IN', 'ISO-639': 'gu' },
german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' }, 'guyanese creole english': { IETF: 'gyn-GY', 'ISO-639': 'gyn' },
'Low German': { IETF: 'nl-NL', ISO639: 'nds', ISO3166: 'nl' }, 'haitian creole french': { IETF: 'ht-HT', 'ISO-639': 'ht' },
gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' }, 'halh mongolian': { IETF: 'khk-MN', 'ISO-639': 'khk' },
glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' }, hausa: { IETF: 'ha-NE', 'ISO-639': 'ha' },
greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' }, hawaiian: { IETF: 'haw-US', 'ISO-639': 'haw' },
'grenadian creole english': { IETF: 'gcl-GD', ISO639: 'gcl', ISO3166: 'gd' }, hebrew: { IETF: 'he-IL', 'ISO-639': 'he' },
guarani: { IETF: 'gn-PY', ISO639: 'gn', ISO3166: 'py' }, higi: { IETF: 'hig-NG', 'ISO-639': 'hig' },
gujarati: { IETF: 'gu-IN', ISO639: 'gu', ISO3166: 'in' }, hiligaynon: { IETF: 'hil-PH', 'ISO-639': 'hil' },
'guyanese creole english': { IETF: 'gyn-GY', ISO639: 'gyn', ISO3166: 'gy' }, 'hill mari': { IETF: 'mrj-RU', 'ISO-639': 'mrj' },
'haitian creole french': { IETF: 'ht-HT', ISO639: 'ht', ISO3166: 'ht' }, hindi: { IETF: 'hi-IN', 'ISO-639': 'hi' },
'halh mongolian': { IETF: 'khk-MN', ISO639: 'khk', ISO3166: 'mn' }, hmong: { IETF: 'hmn-CN', 'ISO-639': 'hmn' },
hausa: { IETF: 'ha-NE', ISO639: 'ha', ISO3166: 'ne' }, hungarian: { IETF: 'hu-HU', 'ISO-639': 'hu' },
hawaiian: { IETF: 'haw-US', ISO639: 'haw', ISO3166: 'xx' }, icelandic: { IETF: 'is-IS', 'ISO-639': 'is' },
hebrew: { IETF: 'he-IL', ISO639: 'he', ISO3166: 'il' }, 'igbo ibo': { IETF: 'ibo-NG', 'ISO-639': 'ibo' },
higi: { IETF: 'hig-NG', ISO639: 'hig', ISO3166: 'ng' }, 'igbo ig': { IETF: 'ig-NG', 'ISO-639': 'ig' },
hiligaynon: { IETF: 'hil-PH', ISO639: 'hil', ISO3166: 'ph' }, ilocano: { IETF: 'ilo-PH', 'ISO-639': 'ilo' },
'hill mari': { IETF: 'mrj-RU', ISO639: 'mrj', ISO3166: 'xx' }, indonesian: { IETF: 'id-ID', 'ISO-639': 'id' },
hmong: { IETF: 'hmn-CN', ISO639: 'hmn', ISO3166: 'cn' }, 'inuktitut greenlandic': { IETF: 'kl-GL', 'ISO-639': 'kl' },
hungarian: { IETF: 'hu-HU', ISO639: 'hu', ISO3166: 'hu' }, 'irish gaelic': { IETF: 'ga-IE', 'ISO-639': 'ga' },
icelandic: { IETF: 'is-IS', ISO639: 'is', ISO3166: 'is' }, italian: { IETF: 'it-IT', 'ISO-639': 'it' },
'igbo ibo': { IETF: 'ibo-NG', ISO639: 'ibo', ISO3166: 'ng' }, 'italian swiss': { IETF: 'it-CH', 'ISO-639': 'it' },
'igbo ig': { IETF: 'ig-NG', ISO639: 'ig', ISO3166: 'ng' }, 'jamaican creole english': { IETF: 'jam-JM', 'ISO-639': 'jam' },
ilocano: { IETF: 'ilo-PH', ISO639: 'ilo', ISO3166: 'ph' }, japanese: { IETF: 'ja-JP', 'ISO-639': 'ja' },
'inuktitut greenlandic': { IETF: 'kl-GL', ISO639: 'kl', ISO3166: 'gl' }, javanese: { IETF: 'jv-ID', 'ISO-639': 'jv' },
'irish gaelic': { IETF: 'ga-IE', ISO639: 'ga', ISO3166: 'ie' }, jingpho: { IETF: 'kac-MM', 'ISO-639': 'kac' },
italian: { IETF: 'it-IT', ISO639: 'it', ISO3166: 'it' }, "k'iche'": { IETF: 'quc-GT', 'ISO-639': 'quc' },
'italian swiss': { IETF: 'it-CH', ISO639: 'it', ISO3166: 'ch' }, 'kabiy<69>': { IETF: 'kbp-TG', 'ISO-639': 'kbp' },
'jamaican creole english': { IETF: 'jam-JM', ISO639: 'jam', ISO3166: 'jm' }, kabuverdianu: { IETF: 'kea-CV', 'ISO-639': 'kea' },
japanese: { IETF: 'ja-JP', ISO639: 'ja', ISO3166: 'jp' }, kabylian: { IETF: 'kab-DZ', 'ISO-639': 'kab' },
javanese: { IETF: 'jv-ID', ISO639: 'jv', ISO3166: 'id' }, kalenjin: { IETF: 'kln-KE', 'ISO-639': 'kln' },
jingpho: { IETF: 'kac-MM', ISO639: 'kac', ISO3166: 'mm' }, kamba: { IETF: 'kam-KE', 'ISO-639': 'kam' },
"k'iche'": { IETF: 'quc-GT', ISO639: 'quc', ISO3166: 'gt' }, kannada: { IETF: 'kn-IN', 'ISO-639': 'kn' },
kabiye: { IETF: 'kbp-TG', ISO639: 'kbp', ISO3166: 'tg' }, kanuri: { IETF: 'kr-KAU', 'ISO-639': 'kr' },
kabuverdianu: { IETF: 'kea-CV', ISO639: 'kea', ISO3166: 'cv' }, karen: { IETF: 'kar-MM', 'ISO-639': 'kar' },
kabylian: { IETF: 'kab-DZ', ISO639: 'kab', ISO3166: 'dz' }, 'kashmiri (devanagari script)': { IETF: 'ks-IN', 'ISO-639': 'ks' },
kalenjin: { IETF: 'kln-KE', ISO639: 'kln', ISO3166: 'ke' }, 'kashmiri (arabic script)': { IETF: 'kas-IN', 'ISO-639': 'kas' },
kamba: { IETF: 'kam-KE', ISO639: 'kam', ISO3166: 'ke' }, kazakh: { IETF: 'kk-KZ', 'ISO-639': 'kk' },
kannada: { IETF: 'kn-IN', ISO639: 'kn', ISO3166: 'in' }, khasi: { IETF: 'kha-IN', 'ISO-639': 'kha' },
kanuri: { IETF: 'kr-KAU', ISO639: 'kr', ISO3166: 'xx' }, khmer: { IETF: 'km-KH', 'ISO-639': 'km' },
karen: { IETF: 'kar-MM', ISO639: 'kar', ISO3166: 'mm' }, 'kikuyu kik': { IETF: 'kik-KE', 'ISO-639': 'kik' },
'kashmiri (devanagari script)': { IETF: 'ks-IN', ISO639: 'ks', ISO3166: 'in' }, 'kikuyu ki': { IETF: 'ki-KE', 'ISO-639': 'ki' },
'kashmiri (arabic script)': { IETF: 'kas-IN', ISO639: 'kas', ISO3166: 'in' }, kimbundu: { IETF: 'kmb-AO', 'ISO-639': 'kmb' },
kazakh: { IETF: 'kk-KZ', ISO639: 'kk', ISO3166: 'kz' }, kinyarwanda: { IETF: 'rw-RW', 'ISO-639': 'rw' },
khasi: { IETF: 'kha-IN', ISO639: 'kha', ISO3166: 'in' }, kirundi: { IETF: 'rn-BI', 'ISO-639': 'rn' },
khmer: { IETF: 'km-KH', ISO639: 'km', ISO3166: 'kh' }, kisii: { IETF: 'guz-KE', 'ISO-639': 'guz' },
'kikuyu kik': { IETF: 'kik-KE', ISO639: 'kik', ISO3166: 'ke' }, kongo: { IETF: 'kg-CG', 'ISO-639': 'kg' },
'kikuyu ki': { IETF: 'ki-KE', ISO639: 'ki', ISO3166: 'ke' }, konkani: { IETF: 'kok-IN', 'ISO-639': 'kok' },
kimbundu: { IETF: 'kmb-AO', ISO639: 'kmb', ISO3166: 'ao' }, korean: { IETF: 'ko-KR', 'ISO-639': 'ko' },
kinyarwanda: { IETF: 'rw-RW', ISO639: 'rw', ISO3166: 'rw' }, 'northern kurdish': { IETF: 'kmr-TR', 'ISO-639': 'kmr' },
kirundi: { IETF: 'rn-BI', ISO639: 'rn', ISO3166: 'bi' }, 'kurdish sorani': { IETF: 'ckb-IQ', 'ISO-639': 'ckb' },
kisii: { IETF: 'guz-KE', ISO639: 'guz', ISO3166: 'ke' }, kyrgyz: { IETF: 'ky-KG', 'ISO-639': 'ky' },
kongo: { IETF: 'kg-CG', ISO639: 'kg', ISO3166: 'cg' }, lao: { IETF: 'lo-LA', 'ISO-639': 'lo' },
konkani: { IETF: 'kok-IN', ISO639: 'kok', ISO3166: 'in' }, latgalian: { IETF: 'ltg-LV', 'ISO-639': 'ltg' },
korean: { IETF: 'ko-KR', ISO639: 'ko', ISO3166: 'kr' }, latin: { IETF: 'la-XN', 'ISO-639': 'la' },
'northern kurdish': { IETF: 'kmr-TR', ISO639: 'kmr', ISO3166: 'tr' }, latvian: { IETF: 'lv-LV', 'ISO-639': 'lv' },
'kurdish sorani': { IETF: 'ckb-IQ', ISO639: 'ckb', ISO3166: 'iq' }, ligurian: { IETF: 'lij-IT', 'ISO-639': 'lij' },
kyrgyz: { IETF: 'ky-KG', ISO639: 'ky', ISO3166: 'kg' }, limburgish: { IETF: 'li-NL', 'ISO-639': 'li' },
lao: { IETF: 'lo-LA', ISO639: 'lo', ISO3166: 'la' }, lingala: { IETF: 'ln-LIN', 'ISO-639': 'ln' },
latgalian: { IETF: 'ltg-LV', ISO639: 'ltg', ISO3166: 'lv' }, lithuanian: { IETF: 'lt-LT', 'ISO-639': 'lt' },
latin: { IETF: 'la-XN', ISO639: 'la', ISO3166: 'xx' }, lombard: { IETF: 'lmo-IT', 'ISO-639': 'lmo' },
latvian: { IETF: 'lv-LV', ISO639: 'lv', ISO3166: 'lg' }, 'luba-kasai': { IETF: 'lua-CD', 'ISO-639': 'lua' },
ligurian: { IETF: 'lij-IT', ISO639: 'lij', ISO3166: 'it' }, luganda: { IETF: 'lg-UG', 'ISO-639': 'lg' },
limburgish: { IETF: 'li-NL', ISO639: 'li', ISO3166: 'nl' }, luhya: { IETF: 'luy-KE', 'ISO-639': 'luy' },
lingala: { IETF: 'ln-LIN', ISO639: 'ln', ISO3166: 'cd' }, luo: { IETF: 'luo-KE', 'ISO-639': 'luo' },
lithuanian: { IETF: 'lt-LT', ISO639: 'lt', ISO3166: 'lt' }, luxembourgish: { IETF: 'lb-LU', 'ISO-639': 'lb' },
lombard: { IETF: 'lmo-IT', ISO639: 'lmo', ISO3166: 'it' }, maa: { IETF: 'mas-KE', 'ISO-639': 'mas' },
'luba-kasai': { IETF: 'lua-CD', ISO639: 'lua', ISO3166: 'cd' }, macedonian: { IETF: 'mk-MK', 'ISO-639': 'mk' },
luganda: { IETF: 'lg-UG', ISO639: 'lg', ISO3166: 'ug' }, magahi: { IETF: 'mag-IN', 'ISO-639': 'mag' },
luhya: { IETF: 'luy-KE', ISO639: 'luy', ISO3166: 'ke' }, maithili: { IETF: 'mai-IN', 'ISO-639': 'mai' },
luo: { IETF: 'luo-KE', ISO639: 'luo', ISO3166: 'ke' }, malagasy: { IETF: 'mg-MG', 'ISO-639': 'mg' },
luxembourgish: { IETF: 'lb-LU', ISO639: 'lb', ISO3166: 'lu' }, malay: { IETF: 'ms-MY', 'ISO-639': 'ms' },
maa: { IETF: 'mas-KE', ISO639: 'mas', ISO3166: 'ke' }, malayalam: { IETF: 'ml-IN', 'ISO-639': 'ml' },
macedonian: { IETF: 'mk-MK', ISO639: 'mk', ISO3166: 'mk' }, maldivian: { IETF: 'dv-MV', 'ISO-639': 'dv' },
magahi: { IETF: 'mag-IN', ISO639: 'mag', ISO3166: 'in' }, maltese: { IETF: 'mt-MT', 'ISO-639': 'mt' },
maithili: { IETF: 'mai-IN', ISO639: 'mai', ISO3166: 'in' }, mandara: { IETF: 'mfi-CM', 'ISO-639': 'mfi' },
malagasy: { IETF: 'mg-MG', ISO639: 'mg', ISO3166: 'mg' }, manipuri: { IETF: 'mni-IN', 'ISO-639': 'mni' },
malay: { IETF: 'ms-MY', ISO639: 'ms', ISO3166: 'my' }, 'manx gaelic': { IETF: 'gv-IM', 'ISO-639': 'gv' },
malayalam: { IETF: 'ml-IN', ISO639: 'ml', ISO3166: 'in' }, maori: { IETF: 'mi-NZ', 'ISO-639': 'mi' },
maldivian: { IETF: 'dv-MV', ISO639: 'dv', ISO3166: 'mv' }, marathi: { IETF: 'mr-IN', 'ISO-639': 'mr' },
maltese: { IETF: 'mt-MT', ISO639: 'mt', ISO3166: 'mt' }, margi: { IETF: 'mrt-NG', 'ISO-639': 'mrt' },
mandara: { IETF: 'mfi-CM', ISO639: 'mfi', ISO3166: 'cm' }, mari: { IETF: 'mhr-RU', 'ISO-639': 'mhr' },
manipuri: { IETF: 'mni-IN', ISO639: 'mni', ISO3166: 'in' }, marshallese: { IETF: 'mh-MH', 'ISO-639': 'mh' },
'manx gaelic': { IETF: 'gv-IM', ISO639: 'gv', ISO3166: 'im' }, mende: { IETF: 'men-SL', 'ISO-639': 'men' },
maori: { IETF: 'mi-NZ', ISO639: 'mi', ISO3166: 'nz' }, meru: { IETF: 'mer-KE', 'ISO-639': 'mer' },
marathi: { IETF: 'mr-IN', ISO639: 'mr', ISO3166: 'in' }, mijikenda: { IETF: 'nyf-KE', 'ISO-639': 'nyf' },
margi: { IETF: 'mrt-NG', ISO639: 'mrt', ISO3166: 'ng' }, minangkabau: { IETF: 'min-ID', 'ISO-639': 'min' },
mari: { IETF: 'mhr-RU', ISO639: 'mhr', ISO3166: 'xx' }, mizo: { IETF: 'lus-IN', 'ISO-639': 'lus' },
marshallese: { IETF: 'mh-MH', ISO639: 'mh', ISO3166: 'mh' }, mongolian: { IETF: 'mn-MN', 'ISO-639': 'mn' },
mende: { IETF: 'men-SL', ISO639: 'men', ISO3166: 'sl' }, montenegrin: { IETF: 'sr-ME', 'ISO-639': 'sr' },
meru: { IETF: 'mer-KE', ISO639: 'mer', ISO3166: 'ke' }, morisyen: { IETF: 'mfe-MU', 'ISO-639': 'mfe' },
mijikenda: { IETF: 'nyf-KE', ISO639: 'nyf', ISO3166: 'ke' }, 'moroccan arabic': { IETF: 'ar-MA', 'ISO-639': 'ar' },
minangkabau: { IETF: 'min-ID', ISO639: 'min', ISO3166: 'id' }, mossi: { IETF: 'mos-BF', 'ISO-639': 'mos' },
mizo: { IETF: 'lus-IN', ISO639: 'lus', ISO3166: 'in' }, ndau: { IETF: 'ndc-MZ', 'ISO-639': 'ndc' },
mongolian: { IETF: 'mn-MN', ISO639: 'mn', ISO3166: 'mn' }, ndebele: { IETF: 'nr-ZA', 'ISO-639': 'nr' },
montenegrin: { IETF: 'sr-ME', ISO639: 'sr', ISO3166: 'me' }, nepali: { IETF: 'ne-NP', 'ISO-639': 'ne' },
morisyen: { IETF: 'mfe-MU', ISO639: 'mfe', ISO3166: 'mu' }, 'nigerian fulfulde': { IETF: 'fuv-NG', 'ISO-639': 'fuv' },
'moroccan arabic': { IETF: 'ar-MA', ISO639: 'ar', ISO3166: 'ma' }, niuean: { IETF: 'niu-NU', 'ISO-639': 'niu' },
mossi: { IETF: 'mos-BF', ISO639: 'mos', ISO3166: 'bf' }, 'north azerbaijani': { IETF: 'azj-AZ', 'ISO-639': 'azj' },
ndau: { IETF: 'ndc-MZ', ISO639: 'ndc', ISO3166: 'mz' }, sesotho: { IETF: 'nso-ZA', 'ISO-639': 'nso' },
ndebele: { IETF: 'nr-ZA', ISO639: 'nr', ISO3166: 'za' }, 'northern uzbek': { IETF: 'uzn-UZ', 'ISO-639': 'uzn' },
nepali: { IETF: 'ne-NP', ISO639: 'ne', ISO3166: 'np' }, 'norwegian bokm<6B>l': { IETF: 'nb-NO', 'ISO-639': 'nb' },
'nigerian fulfulde': { IETF: 'fuv-NG', ISO639: 'fuv', ISO3166: 'ng' }, 'norwegian nynorsk': { IETF: 'nn-NO', 'ISO-639': 'nn' },
niuean: { IETF: 'niu-NU', ISO639: 'niu', ISO3166: 'nu' }, nuer: { IETF: 'nus-SS', 'ISO-639': 'nus' },
'north azerbaijani': { IETF: 'azj-AZ', ISO639: 'azj', ISO3166: 'az' }, nyanja: { IETF: 'ny-MW', 'ISO-639': 'ny' },
sesotho: { IETF: 'nso-ZA', ISO639: 'nso', ISO3166: 'za' }, occitan: { IETF: 'oc-FR', 'ISO-639': 'oc' },
'northern uzbek': { IETF: 'uzn-UZ', ISO639: 'uzn', ISO3166: 'uz' }, 'occitan aran': { IETF: 'oc-ES', 'ISO-639': 'oc' },
'norwegian bokm<6B>l': { IETF: 'nb-NO', ISO639: 'nb', ISO3166: 'no' }, odia: { IETF: 'or-IN', 'ISO-639': 'or' },
'norwegian nynorsk': { IETF: 'nn-NO', ISO639: 'nn', ISO3166: 'no' }, oriya: { IETF: 'ory-IN', 'ISO-639': 'ory' },
nuer: { IETF: 'nus-SS', ISO639: 'nus', ISO3166: 'ss' }, urdu: { IETF: 'ur-PK', 'ISO-639': 'ur' },
nyanja: { IETF: 'ny-MW', ISO639: 'ny', ISO3166: 'mw' }, palauan: { IETF: 'pau-PW', 'ISO-639': 'pau' },
occitan: { IETF: 'oc-FR', ISO639: 'oc', ISO3166: 'fr' }, pali: { IETF: 'pi-IN', 'ISO-639': 'pi' },
'occitan aran': { IETF: 'oc-ES', ISO639: 'oc', ISO3166: 'es-ct' }, pangasinan: { IETF: 'pag-PH', 'ISO-639': 'pag' },
odia: { IETF: 'or-IN', ISO639: 'or', ISO3166: 'in' }, papiamentu: { IETF: 'pap-CW', 'ISO-639': 'pap' },
oriya: { IETF: 'ory-IN', ISO639: 'ory', ISO3166: 'in' }, pashto: { IETF: 'ps-PK', 'ISO-639': 'ps' },
urdu: { IETF: 'ur-PK', ISO639: 'ur', ISO3166: 'pk' }, persian: { IETF: 'fa-IR', 'ISO-639': 'fa' },
palauan: { IETF: 'pau-PW', ISO639: 'pau', ISO3166: 'pw' }, pijin: { IETF: 'pis-SB', 'ISO-639': 'pis' },
pali: { IETF: 'pi-IN', ISO639: 'pi', ISO3166: 'in' }, 'plateau malagasy': { IETF: 'plt-MG', 'ISO-639': 'plt' },
pangasinan: { IETF: 'pag-PH', ISO639: 'pag', ISO3166: 'ph' }, polish: { IETF: 'pl-PL', 'ISO-639': 'pl' },
papiamentu: { IETF: 'pap-CW', ISO639: 'pap', ISO3166: 'cw' }, portuguese: { IETF: 'pt-PT', 'ISO-639': 'pt' },
pashto: { IETF: 'ps-PK', ISO639: 'ps', ISO3166: 'pk' }, 'portuguese brazil': { IETF: 'pt-BR', 'ISO-639': 'pt' },
persian: { IETF: 'fa-IR', ISO639: 'fa', ISO3166: 'ir' }, potawatomi: { IETF: 'pot-US', 'ISO-639': 'pot' },
pijin: { IETF: 'pis-SB', ISO639: 'pis', ISO3166: 'sb' }, punjabi: { IETF: 'pa-IN', 'ISO-639': 'pa' },
'plateau malagasy': { IETF: 'plt-MG', ISO639: 'plt', ISO3166: 'mg' }, 'punjabi (pakistan)': { IETF: 'pnb-PK', 'ISO-639': 'pnb' },
polish: { IETF: 'pl-PL', ISO639: 'pl', ISO3166: 'pl' }, quechua: { IETF: 'qu-PE', 'ISO-639': 'qu' },
portuguese: { IETF: 'pt-PT', ISO639: 'pt', ISO3166: 'pt' }, rohingya: { IETF: 'rhg-MM', 'ISO-639': 'rhg' },
'portuguese brazil': { IETF: 'pt-BR', ISO639: 'pt', ISO3166: 'br' }, rohingyalish: { IETF: 'rhl-MM', 'ISO-639': 'rhl' },
potawatomi: { IETF: 'pot-US', ISO639: 'pot', ISO3166: 'us' }, romanian: { IETF: 'ro-RO', 'ISO-639': 'ro' },
punjabi: { IETF: 'pa-IN', ISO639: 'pa', ISO3166: 'in' }, romansh: { IETF: 'roh-CH', 'ISO-639': 'roh' },
'punjabi (pakistan)': { IETF: 'pnb-PK', ISO639: 'pnb', ISO3166: 'pk' }, rundi: { IETF: 'run-BI', 'ISO-639': 'run' },
quechua: { IETF: 'qu-PE', ISO639: 'qu', ISO3166: 'pe' }, russian: { IETF: 'ru-RU', 'ISO-639': 'ru' },
rohingya: { IETF: 'rhg-MM', ISO639: 'rhg', ISO3166: 'mm' }, 'saint lucian creole french': { IETF: 'acf-LC', 'ISO-639': 'acf' },
rohingyalish: { IETF: 'rhl-MM', ISO639: 'rhl', ISO3166: 'mm' }, samoan: { IETF: 'sm-WS', 'ISO-639': 'sm' },
romanian: { IETF: 'ro-RO', ISO639: 'ro', ISO3166: 'ro' }, sango: { IETF: 'sg-CF', 'ISO-639': 'sg' },
romansh: { IETF: 'roh-CH', ISO639: 'roh', ISO3166: 'ch' }, sanskrit: { IETF: 'sa-IN', 'ISO-639': 'sa' },
rundi: { IETF: 'run-BI', ISO639: 'run', ISO3166: 'bi' }, santali: { IETF: 'sat-IN', 'ISO-639': 'sat' },
'saint lucian creole french': { IETF: 'acf-LC', ISO639: 'acf', ISO3166: 'lc' }, sardinian: { IETF: 'sc-IT', 'ISO-639': 'sc' },
samoan: { IETF: 'sm-WS', ISO639: 'sm', ISO3166: 'ws' }, 'scots gaelic': { IETF: 'gd-GB', 'ISO-639': 'gd' },
sango: { IETF: 'sg-CF', ISO639: 'sg', ISO3166: 'cf' }, sena: { IETF: 'seh-ZW', 'ISO-639': 'seh' },
sanskrit: { IETF: 'sa-IN', ISO639: 'sa', ISO3166: 'in' }, 'serbian cyrillic': { IETF: 'sr-Cyrl-RS', 'ISO-639': 'sr' },
santali: { IETF: 'sat-IN', ISO639: 'sat', ISO3166: 'in' }, 'serbian latin': { IETF: 'sr-Latn-RS', 'ISO-639': 'sr' },
sardinian: { IETF: 'sc-IT', ISO639: 'sc', ISO3166: 'it' }, 'seselwa creole french': { IETF: 'crs-SC', 'ISO-639': 'crs' },
'scots gaelic': { IETF: 'gd-GB', ISO639: 'gd', ISO3166: 'gb-sct' }, 'setswana (south africa)': { IETF: 'tn-ZA', 'ISO-639': 'tn' },
sena: { IETF: 'seh-ZW', ISO639: 'seh', ISO3166: 'zw' }, shan: { IETF: 'shn-MM', 'ISO-639': 'shn' },
'serbian cyrillic': { IETF: 'sr-Cyrl-RS', ISO639: 'sr', ISO3166: 'rs' }, shona: { IETF: 'sn-ZW', 'ISO-639': 'sn' },
'serbian latin': { IETF: 'sr-Latn-RS', ISO639: 'sr', ISO3166: 'rs' }, sicilian: { IETF: 'scn-IT', 'ISO-639': 'scn' },
'seselwa creole french': { IETF: 'crs-SC', ISO639: 'crs', ISO3166: 'sc' }, silesian: { IETF: 'szl-PL', 'ISO-639': 'szl' },
'setswana (south africa)': { IETF: 'tn-ZA', ISO639: 'tn', ISO3166: 'za' }, 'sindhi snd': { IETF: 'snd-PK', 'ISO-639': 'snd' },
shan: { IETF: 'shn-MM', ISO639: 'shn', ISO3166: 'mm' }, 'sindhi sd': { IETF: 'sd-PK', 'ISO-639': 'sd' },
shona: { IETF: 'sn-ZW', ISO639: 'sn', ISO3166: 'zw' }, sinhala: { IETF: 'si-LK', 'ISO-639': 'si' },
sicilian: { IETF: 'scn-IT', ISO639: 'scn', ISO3166: 'it' }, slovak: { IETF: 'sk-SK', 'ISO-639': 'sk' },
silesian: { IETF: 'szl-PL', ISO639: 'szl', ISO3166: 'pl' }, slovenian: { IETF: 'sl-SI', 'ISO-639': 'sl' },
'sindhi snd': { IETF: 'snd-PK', ISO639: 'snd', ISO3166: 'pk' }, somali: { IETF: 'so-SO', 'ISO-639': 'so' },
'sindhi sd': { IETF: 'sd-PK', ISO639: 'sd', ISO3166: 'pk' }, 'sotho southern': { IETF: 'st-LS', 'ISO-639': 'st' },
sinhala: { IETF: 'si-LK', ISO639: 'si', ISO3166: 'lk' }, 'south azerbaijani': { IETF: 'azb-AZ', 'ISO-639': 'azb' },
slovak: { IETF: 'sk-SK', ISO639: 'sk', ISO3166: 'sk' }, 'southern pashto': { IETF: 'pbt-PK', 'ISO-639': 'pbt' },
slovenian: { IETF: 'sl-SI', ISO639: 'sl', ISO3166: 'si' }, 'southwestern dinka': { IETF: 'dik-SS', 'ISO-639': 'dik' },
somali: { IETF: 'so-SO', ISO639: 'so', ISO3166: 'so' }, spanish: { IETF: 'es-ES', 'ISO-639': 'es' },
'sotho southern': { IETF: 'st-LS', ISO639: 'st', ISO3166: 'ls' }, 'spanish argentina': { IETF: 'es-AR', 'ISO-639': 'es' },
'south azerbaijani': { IETF: 'azb-AZ', ISO639: 'azb', ISO3166: 'az' }, 'spanish colombia': { IETF: 'es-CO', 'ISO-639': 'es' },
'southern pashto': { IETF: 'pbt-PK', ISO639: 'pbt', ISO3166: 'pk' }, 'spanish latin america': { IETF: 'es-419', 'ISO-639': 'es' },
'southwestern dinka': { IETF: 'dik-SS', ISO639: 'dik', ISO3166: 'ss' }, 'spanish mexico': { IETF: 'es-MX', 'ISO-639': 'es' },
'spanish argentina': { IETF: 'es-AR', ISO639: 'es', ISO3166: 'ar' }, 'spanish united states': { IETF: 'es-US', 'ISO-639': 'es' },
'spanish colombia': { IETF: 'es-CO', ISO639: 'es', ISO3166: 'co' }, 'sranan tongo': { IETF: 'srn-SR', 'ISO-639': 'srn' },
'spanish latin america': { IETF: 'es-419', ISO639: 'es', ISO3166: 'do' }, 'standard latvian': { IETF: 'lvs-LV', 'ISO-639': 'lvs' },
'spanish mexico': { IETF: 'es-MX', ISO639: 'es', ISO3166: 'mx' }, 'standard malay': { IETF: 'zsm-MY', 'ISO-639': 'zsm' },
'spanish united states': { IETF: 'es-US', ISO639: 'es', ISO3166: 'es' }, sundanese: { IETF: 'su-ID', 'ISO-639': 'su' },
'sranan tongo': { IETF: 'srn-SR', ISO639: 'srn', ISO3166: 'sr' }, swahili: { IETF: 'sw-KE', 'ISO-639': 'sw' },
'standard latvian': { IETF: 'lvs-LV', ISO639: 'lvs', ISO3166: 'lv' }, swati: { IETF: 'ss-SZ', 'ISO-639': 'ss' },
'standard malay': { IETF: 'zsm-MY', ISO639: 'zsm', ISO3166: 'my' }, swedish: { IETF: 'sv-SE', 'ISO-639': 'sv' },
sundanese: { IETF: 'su-ID', ISO639: 'su', ISO3166: 'id' }, 'swiss german': { IETF: 'de-CH', 'ISO-639': 'de' },
swahili: { IETF: 'sw-KE', ISO639: 'sw', ISO3166: 'ke' }, 'syriac (aramaic)': { IETF: 'syc-TR', 'ISO-639': 'syc' },
swati: { IETF: 'ss-SZ', ISO639: 'ss', ISO3166: 'sz' }, tagalog: { IETF: 'tl-PH', 'ISO-639': 'tl' },
swedish: { IETF: 'sv-SE', ISO639: 'sv', ISO3166: 'se' }, tahitian: { IETF: 'ty-PF', 'ISO-639': 'ty' },
'swiss german': { IETF: 'de-CH', ISO639: 'de', ISO3166: 'ch' }, tajik: { IETF: 'tg-TJ', 'ISO-639': 'tg' },
'syriac (aramaic)': { IETF: 'syc-TR', ISO639: 'syc', ISO3166: 'tr' }, 'tamashek (tuareg)': { IETF: 'tmh-DZ', 'ISO-639': 'tmh' },
tagalog: { IETF: 'tl-PH', ISO639: 'tl', ISO3166: 'ph' }, tamasheq: { IETF: 'taq-ML', 'ISO-639': 'taq' },
tahitian: { IETF: 'ty-PF', ISO639: 'ty', ISO3166: 'pf' }, 'tamil india': { IETF: 'ta-IN', 'ISO-639': 'ta' },
tajik: { IETF: 'tg-TJ', ISO639: 'tg', ISO3166: 'tj' }, 'tamil sri lanka': { IETF: 'ta-LK', 'ISO-639': 'ta' },
'tamashek (tuareg)': { IETF: 'tmh-DZ', ISO639: 'tmh', ISO3166: 'dz' }, taroko: { IETF: 'trv-TW', 'ISO-639': 'trv' },
tamasheq: { IETF: 'taq-ML', ISO639: 'taq', ISO3166: 'ml' }, tatar: { IETF: 'tt-RU', 'ISO-639': 'tt' },
'tamil india': { IETF: 'ta-IN', ISO639: 'ta', ISO3166: 'in' }, telugu: { IETF: 'te-IN', 'ISO-639': 'te' },
'tamil sri lanka': { IETF: 'ta-LK', ISO639: 'ta', ISO3166: 'lk' }, tetum: { IETF: 'tet-TL', 'ISO-639': 'tet' },
taroko: { IETF: 'trv-TW', ISO639: 'trv', ISO3166: 'tw' }, thai: { IETF: 'th-TH', 'ISO-639': 'th' },
tatar: { IETF: 'tt-RU', ISO639: 'tt', ISO3166: 'ru' }, tibetan: { IETF: 'bo-CN', 'ISO-639': 'bo' },
telugu: { IETF: 'te-IN', ISO639: 'te', ISO3166: 'in' }, tigrinya: { IETF: 'ti-ET', 'ISO-639': 'ti' },
tetum: { IETF: 'tet-TL', ISO639: 'tet', ISO3166: 'tl' }, 'tok pisin': { IETF: 'tpi-PG', 'ISO-639': 'tpi' },
thai: { IETF: 'th-TH', ISO639: 'th', ISO3166: 'th' }, tokelauan: { IETF: 'tkl-TK', 'ISO-639': 'tkl' },
tibetan: { IETF: 'bo-CN', ISO639: 'bo', ISO3166: 'cn' }, tongan: { IETF: 'to-TO', 'ISO-639': 'to' },
tigrinya: { IETF: 'ti-ET', ISO639: 'ti', ISO3166: 'et' }, 'tosk albanian': { IETF: 'als-AL', 'ISO-639': 'als' },
'tok pisin': { IETF: 'tpi-PG', ISO639: 'tpi', ISO3166: 'pg' }, tsonga: { IETF: 'ts-ZA', 'ISO-639': 'ts' },
tokelauan: { IETF: 'tkl-TK', ISO639: 'tkl', ISO3166: 'tk' }, tswa: { IETF: 'tsc-MZ', 'ISO-639': 'tsc' },
tongan: { IETF: 'to-TO', ISO639: 'to', ISO3166: 'to' }, tswana: { IETF: 'tn-BW', 'ISO-639': 'tn' },
'tosk albanian': { IETF: 'als-AL', ISO639: 'als', ISO3166: 'al' }, tumbuka: { IETF: 'tum-MW', 'ISO-639': 'tum' },
tsonga: { IETF: 'ts-ZA', ISO639: 'ts', ISO3166: 'za' }, turkish: { IETF: 'tr-TR', 'ISO-639': 'tr' },
tswa: { IETF: 'tsc-MZ', ISO639: 'tsc', ISO3166: 'mz' }, turkmen: { IETF: 'tk-TM', 'ISO-639': 'tk' },
tswana: { IETF: 'tn-BW', ISO639: 'tn', ISO3166: 'bw' }, tuvaluan: { IETF: 'tvl-TV', 'ISO-639': 'tvl' },
tumbuka: { IETF: 'tum-MW', ISO639: 'tum', ISO3166: 'mw' }, twi: { IETF: 'tw-GH', 'ISO-639': 'tw' },
turkmen: { IETF: 'tk-TM', ISO639: 'tk', ISO3166: 'tm' }, udmurt: { IETF: 'udm-RU', 'ISO-639': 'udm' },
tuvaluan: { IETF: 'tvl-TV', ISO639: 'tvl', ISO3166: 'tv' }, ukrainian: { IETF: 'uk-UA', 'ISO-639': 'uk' },
twi: { IETF: 'tw-GH', ISO639: 'tw', ISO3166: 'gh' }, uma: { IETF: 'ppk-ID', 'ISO-639': 'ppk' },
udmurt: { IETF: 'udm-RU', ISO639: 'udm', ISO3166: 'xx' }, umbundu: { IETF: 'umb-AO', 'ISO-639': 'umb' },
ukrainian: { IETF: 'uk-UA', ISO639: 'uk', ISO3166: 'ua' }, 'uyghur uig': { IETF: 'uig-CN', 'ISO-639': 'uig' },
uma: { IETF: 'ppk-ID', ISO639: 'ppk', ISO3166: 'id' }, 'uyghur ug': { IETF: 'ug-CN', 'ISO-639': 'ug' },
umbundu: { IETF: 'umb-AO', ISO639: 'umb', ISO3166: 'ao' }, uzbek: { IETF: 'uz-UZ', 'ISO-639': 'uz' },
'uyghur uig': { IETF: 'uig-CN', ISO639: 'uig', ISO3166: 'cn' }, venetian: { IETF: 'vec-IT', 'ISO-639': 'vec' },
'uyghur ug': { IETF: 'ug-CN', ISO639: 'ug', ISO3166: 'cn' }, vietnamese: { IETF: 'vi-VN', 'ISO-639': 'vi' },
uzbek: { IETF: 'uz-UZ', ISO639: 'uz', ISO3166: 'uz' }, 'vincentian creole english': { IETF: 'svc-VC', 'ISO-639': 'svc' },
venetian: { IETF: 'vec-IT', ISO639: 'vec', ISO3166: 'it' }, 'virgin islands creole english': { IETF: 'vic-US', 'ISO-639': 'vic' },
vietnamese: { IETF: 'vi-VN', ISO639: 'vi', ISO3166: 'vn' }, wallisian: { IETF: 'wls-WF', 'ISO-639': 'wls' },
'vincentian creole english': { IETF: 'svc-VC', ISO639: 'svc', ISO3166: 'vc' }, 'waray (philippines)': { IETF: 'war-PH', 'ISO-639': 'war' },
'virgin islands creole english': { IETF: 'vic-US', ISO639: 'vic', ISO3166: 'vi' }, welsh: { IETF: 'cy-GB', 'ISO-639': 'cy' },
wallisian: { IETF: 'wls-WF', ISO639: 'wls', ISO3166: 'wf' }, 'west central oromo': { IETF: 'gaz-ET', 'ISO-639': 'gaz' },
'waray (philippines)': { IETF: 'war-PH', ISO639: 'war', ISO3166: 'ph' }, 'western persian': { IETF: 'pes-IR', 'ISO-639': 'pes' },
welsh: { IETF: 'cy-GB', ISO639: 'cy', ISO3166: 'gb-wls' }, wolof: { IETF: 'wo-SN', 'ISO-639': 'wo' },
'west central oromo': { IETF: 'gaz-ET', ISO639: 'gaz', ISO3166: 'et' }, xhosa: { IETF: 'xh-ZA', 'ISO-639': 'xh' },
'western persian': { IETF: 'pes-IR', ISO639: 'pes', ISO3166: 'ir' }, yiddish: { IETF: 'yi-YD', 'ISO-639': 'yi' },
wolof: { IETF: 'wo-SN', ISO639: 'wo', ISO3166: 'sn' }, yoruba: { IETF: 'yo-NG', 'ISO-639': 'yo' },
xhosa: { IETF: 'xh-ZA', ISO639: 'xh', ISO3166: 'za' }, zulu: { IETF: 'zu-ZA', 'ISO-639': 'zu' },
yiddish: { IETF: 'yi-YD', ISO639: 'yi', ISO3166: 'il' },
yoruba: { IETF: 'yo-NG', ISO639: 'yo', ISO3166: 'ng' },
zulu: { IETF: 'zu-ZA', ISO639: 'zu', ISO3166: 'za' }
}; };
module.exports = { languages }; module.exports = { languages };

View file

@ -6,75 +6,73 @@ const path = require('path');
const consoleloggerLevel = process.env.WINSTON_LOGGER_LEVEL || 'info'; const consoleloggerLevel = process.env.WINSTON_LOGGER_LEVEL || 'info';
const consoleFormat = format.combine( 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({
level: 'info', level: 'info',
format: fileFormat, format: fileFormat,
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');
const levelCell = document.createElement('td'); const levelCell = document.createElement('td');
levelCell.textContent = logObject.level; levelCell.textContent = logObject.level;
levelCell.classList.add(logObject.level); // Add class for styling levelCell.classList.add(logObject.level); // Add class for styling
row.appendChild(levelCell); row.appendChild(levelCell);
const messageCell = document.createElement('td'); const messageCell = document.createElement('td');
messageCell.textContent = logObject.message; messageCell.textContent = logObject.message;
row.appendChild(messageCell); row.appendChild(messageCell);
const metadataCell = document.createElement('td'); const metadataCell = document.createElement('td');
metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2); metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2);
row.appendChild(metadataCell); row.appendChild(metadataCell);
const timestampCell = document.createElement('td'); const timestampCell = document.createElement('td');
timestampCell.textContent = logObject.timestamp; timestampCell.textContent = logObject.timestamp;
row.appendChild(timestampCell); row.appendChild(timestampCell);
tableBody.appendChild(row); tableBody.appendChild(row);
}); });
}) })
.catch(error => { .catch((error) => {});
console.error(error);
});
module.exports = logger; module.exports = logger;

View file

@ -1,51 +1,49 @@
/* global settings, */ let micSelect = document.querySelector('#microphone');
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);
}); });
}); });
} }
// Microphones // Microphones
getAvailableMediaDevices('audioinput') getAvailableMediaDevices('audioinput')
.then(microphones => { .then((microphones) => {
let i = 0; let i = 0;
let tempname = ''; let tempname = '';
for (const mic of microphones) { for (let 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.
} }
if (mic.deviceId === 'communications' || mic.label === tempname) { if (mic.deviceId === 'communications' || mic.label === tempname) {
continue; continue;
} }
const option = document.createElement('option'); const option = document.createElement('option');
// Set the options value and text. // Set the options value and text.
option.value = i; option.value = i;
option.innerHTML = `${mic.label}`; option.innerHTML = `${mic.label}`;
// Add the option to the voice selector. // Add the option to the voice selector.
micSelect.appendChild(option); micSelect.appendChild(option);
if (i === microphones.length - 1) { if (i === microphones.length - 1) {
document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE; document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE;
} }
i++; i++;
} }
}) })
.catch(error => { .catch((error) => {
console.error('Error retrieving microphones:', error); console.error('Error retrieving microphones:', error);
}); });

View file

@ -1,51 +1,42 @@
const twitchTemplate = ` const twitchTemplate = `
<img class="user-img" src="" /> <div class="icon-container">
<img class="status-circle sender" src="./images/twitch-icon.png" tip="Twitch" /> <img class="user-img" src="" />
<span class="post-time sender"></span> <img class="status-circle" src="./images/twitch-icon.png" />
<span class="username sender"></span> </div>
<div class="msg-box sender"></div> <span class="username"></span>
`.trim(); <div class="msg-box">
</div>
const trovoTemplate = `
<img class="user-img" src="" />
<img class="status-circle sender" src="./images/trovo-icon.jpg" tip="Trovo" />
<span class="post-time sender"></span>
<span class="username sender"></span>
<div class="msg-box sender"></div>
`.trim();
const youtubeTemplate = `
<img class="user-img" src="" />
<img class="status-circle sender" src="./images/youtube-icon.png" tip="Youtube" />
<span class="post-time sender"></span>
<span class="username sender"></span>
<div class="msg-box sender"></div>
`.trim();
const dliveTemplate = `
<img class="user-img" src="" />
<img class="status-circle sender" src="./images/dlive-icon.png" tip="DLive" />
<span class="post-time sender"></span>
<span class="username sender"></span>
<div class="msg-box sender"></div>
`.trim(); `.trim();
const userTemplate = ` const userTemplate = `
<img class="user-img" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" /> <div class="icon-container-user">
<img class="status-circle user" src="./images/twitch-icon.png" /> <span class="post-time-user">You</span>
<span class="post-time user"></span> <img class="status-circle-user" src="./images/twitch-icon.png" />
<span class="username user">You</span> <img class="user-img-user" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
<div class="msg-box user"></div> </div>
<span class="username-user">You</span>
<div class="msg-box-user">
</div>
`.trim(); `.trim();
const messageTemplate = ` const messageTemplate = `
<article class=" user"> <article class="msg-container msg-self" id="msg-0">
<img class="user-img" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" /> <div class="icon-container-user">
<img class="status-circle user" src="./images/twitch-icon.png" /> <img class="user-img-user" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
<span class="post-time user"> 12:00 PM</span> <img class="status-circle-user" src="./images/twitch-icon.png" />
<span class="username user">You</span> </div>
<div class="msg-box user">Hello there</div> <div class="msg-box-user msg-box-user-temp">
<div class="flr">
<div class="messages-user">
<span class="timestamp timestamp-temp"><span class="username username-temp">You</span><span class="posttime">${getPostTime()}</span></span>
<br>
<p class="msg msg-temp" id="msg-0">
hello there
</p>
</div>
</div>
</div>
</article> </article>
`.trim(); `.trim();
module.exports = { twitchTemplate, dliveTemplate, youtubeTemplate, userTemplate, messageTemplate, trovoTemplate }; module.exports = { twitchTemplate, userTemplate, messageTemplate };

View file

@ -1,12 +1,14 @@
/* eslint-disable no-unused-vars */
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const ini = require('ini');
const path = require('path'); // get directory path
const axios = require('axios'); const axios = require('axios');
const Sockette = require('sockette');
const { webFrame, ipcRenderer, shell } = require('electron'); const { 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 exec = util.promisify(require('child_process').exec);
const GoogleTTS = require('node-google-tts-api'); const GoogleTTS = require('node-google-tts-api');
const tts = new GoogleTTS(); const tts = new GoogleTTS();
@ -15,16 +17,14 @@ const { Socket } = require('socket.io-client');
const main = ipcRenderer.sendSync('environment'); const main = ipcRenderer.sendSync('environment');
const resourcesPath = main.resourcesPath; const resourcesPath = main.resourcesPath;
const settingsPath = main.settingsPath.toString(); let settingsPath = main.settingsPath.toString();
const pythonPath = main.pythonPath.toString(); let 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
const googleVoices = fs.readFileSync(path.join(__dirname, './config/googleVoices.txt')).toString().split('\r\n'); const googleVoices = fs.readFileSync(path.join(__dirname, './config/googleVoices.txt')).toString().split('\r\n');
// TODO: remove amazon voices txt and use api instead (sakura project has it) // TODO: remove amazon voices txt and use api instead (sakura project has it)
const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n'); const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n');
const customEmotesListSavePath =
main.isPackaged === true ? path.join(resourcesPath, './custom-emotes.json') : path.join(resourcesPath, './config/custom-emotes.json');
// html elements // html elements
const root = document.documentElement; const root = document.documentElement;
@ -35,9 +35,6 @@ 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');
const lol = document.body.querySelector('country-flag-emoji-polyfill');
// 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,43 +44,21 @@ const languageObject = require(path.join(__dirname, './js/languages'));
const logger = require(path.join(__dirname, './js/logger')); const logger = require(path.join(__dirname, './js/logger'));
const sound = require(path.join(__dirname, './js/sound')); const sound = require(path.join(__dirname, './js/sound'));
const config = require(path.join(__dirname, './js/settings')); const config = require(path.join(__dirname, './js/settings'));
const { TokenAutocomplete } = require(path.join(__dirname, './js/token-autocomplete'));
const betterTTVAutocomplete = new TokenAutocomplete({
name: 'sample',
selector: '#sample',
noMatchesText: 'No matching results...',
initialTokens: [...settings.TWITCH.BETTERTTV_CHANNELS]
});
const test = settings.TWITCH.BETTERTTV_CHANNELS;
console.log(test);
const mediaDevices = require(path.join(__dirname, './js/mediaDevices')); const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
const notificationSounds = path.join(resourcesPath, main.isPackaged ? './sounds/notifications' : '../sounds/notifications'); let notificationSounds = path.join(__dirname, './sounds/notifications');
const sttModels = path.join(resourcesPath, main.isPackaged ? './speech_to_text_models' : '../speech_to_text_models'); let sttModels = path.join(__dirname, '../speech_to_text_models');
function reset() { function reset() {
ipcRenderer.send('restart'); ipcRenderer.send('restart');
} }
const server = require(path.join(__dirname, './js/server')); let server = require(path.join(__dirname, './js/server'));
const backend = require(path.join(__dirname, './js/backend')); const backend = require(path.join(__dirname, './js/backend'));
const socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server let socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server
let twitch = null;
twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
let dlive = null;
dlive = settings.DLIVE.USE_DLIVE ? require(path.join(__dirname, './js/dlive')) : '';
let youtube = null;
youtube = settings.YOUTUBE.USE_YOUTUBE ? require(path.join(__dirname, './js/youtube')) : '';
let trovo = null;
trovo = settings.TROVO.USE_TROVO ? require(path.join(__dirname, './js/trovo')) : '';
let 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')) : '';
@ -91,12 +66,6 @@ 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 = [];
let messageId = 0;
messageId = 0;
let customEmojiList = [];
// initialize values // initialize values
config.getGeneralSettings(); config.getGeneralSettings();
@ -108,472 +77,179 @@ const StartDateAndTime = Date.now();
const speakButton = document.querySelector('#speakBtn'); 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) { files.forEach((file, i) => {
console.error(err); // Create a new option element.
} const option = document.createElement('option');
files.forEach((file, i) => { // Set the options value and text.
// Create a new option element. option.value = i;
const option = document.createElement('option'); option.innerHTML = file;
// Set the options value and text. // Add the option to the sound selector.
option.value = i; notificationSound.appendChild(option);
option.innerHTML = file; });
// Add the option to the sound selector. // set the saved notification sound
notificationSound.appendChild(option); notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND;
});
// set the saved notification sound
notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND;
}); });
// Check for installed stt models // Check for installed stt models
fs.readdir(sttModels, (err, files) => { fs.readdir(sttModels, (err, files) => {
if (err) { for (let file of files) {
console.error(err); if (file.includes('.txt')) {
} continue;
}
// Create a new option element.
const option = document.createElement('option');
for (const file of files) { // Set the options value and text.
if (file.includes('.txt')) { option.value = file;
continue; option.innerHTML = file;
// Add the option to the sound selector.
sttModel.appendChild(option);
} }
// Create a new option element.
const option = document.createElement('option');
// Set the options value and text. // set the saved notification sound
option.value = file; sttModel.value = settings.STT.LANGUAGE;
option.innerHTML = file;
// Add the option to the sound selector.
sttModel.appendChild(option);
}
// set the saved notification sound
sttModel.value = settings.STT.LANGUAGE;
}); });
// TODO: refactor obtaining audio devices.
async function getAudioDevices() { async function getAudioDevices() {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
return; return;
} }
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 option1 = document.createElement('option'); const option = document.createElement('option');
const option2 = document.createElement('option'); option.text = device.label || `Output ${device.deviceId}`;
option1.text = device.label || `Output ${device.deviceId}`; option.value = device.deviceId;
option2.text = device.label || `Output ${device.deviceId}`; ttsAudioDevices.appendChild(option);
option1.value = device.deviceId; });
option2.value = device.deviceId;
ttsAudioDevices.appendChild(option1);
notificationSoundAudioDevices.appendChild(option2);
});
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 setSelectedLanguageinSelect(languageSelect, language) {
const button = languageSelect.querySelector('.SmallButton');
const languageElement = document.createElement('span');
languageElement.classList = `fi fi-${language.ISO3166} fis pop-selection`;
languageElement.setAttribute('tip', language.name);
button.innerHTML = '';
button.appendChild(languageElement);
addSingleTooltip(languageElement);
}
function setLanguagesinSelectx(languageSelector, language) {
const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
const languageSelectContent = languageSelect.querySelector('.pop-content');
languageSelectContent.addEventListener('click', e => {
const parent = e.target.parentElement.id;
language = getLanguageProperties(e.target.getAttribute('value'));
if (parent === 'SEND_TRANSLATION_IN') {
settings.LANGUAGE.SEND_TRANSLATION_IN = language.IETF;
} else {
settings.LANGUAGE.SEND_TRANSLATION_OUT = language.IETF;
}
fs.writeFileSync(settingsPath, JSON.stringify(settings));
setSelectedLanguageinSelect(languageSelect, language);
});
for (const language in languageObject.languages) {
if (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) {
const IETF = languageObject.languages[language].IETF;
const ISO639 = languageObject.languages[language].ISO639;
const ISO3166 = languageObject.languages[language].ISO3166;
const option = document.createElement('div');
option.classList = 'language-select';
const languageElement = document.createElement('span');
languageElement.classList = `fi fi-${ISO3166} fis`;
languageElement.style.pointerEvents = 'none';
option.setAttribute('tip', language);
const text = document.createElement('span');
text.style.pointerEvents = 'none';
text.innerHTML = ` - ${ISO639}`;
option.setAttribute('value', IETF);
languageSelectContent.appendChild(option);
option.appendChild(languageElement);
option.appendChild(text);
addSingleTooltip(option);
}
}
setSelectedLanguageinSelect(languageSelect, language);
}
setLanguagesinSelectx('.pop.in', getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_IN));
setLanguagesinSelectx('.pop.out', getLanguageProperties(settings.LANGUAGE.SEND_TRANSLATION_OUT));
function setLanguagesinSelect(languageSelector, setting) { function setLanguagesinSelect(languageSelector, setting) {
const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox let 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 (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) { if (languageObject.languages.hasOwnProperty(language)) {
const IETF = languageObject.languages[language].IETF; const iso639 = languageObject.languages[language]['ISO-639'];
const ISO639 = languageObject.languages[language].ISO639; const option = document.createElement('option');
const option = document.createElement('option'); option.value = iso639;
option.innerHTML = `${iso639} - ${language}`;
option.value = IETF; languageSelect.appendChild(option);
option.innerHTML = `${ISO639} : ${language}`; }
languageSelect.appendChild(option);
} }
}
languageSelect.selectedIndex = setting; languageSelect.selectedIndex = setting;
} }
setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE_INDEX); setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE);
setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX); setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX);
setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX); setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX);
setLanguagesinSelect('#TRANSLATE_TO', settings.LANGUAGE.TRANSLATE_TO_INDEX);
function addVoiceService(name) { function addVoiceService(name) {
function addToselect(select) { function addToselect(select) {
const ttsService = document.querySelector(select); let ttsService = document.querySelector(select);
const option = document.createElement('option'); const option = document.createElement('option');
ttsService.appendChild(option); ttsService.appendChild(option);
option.value = name; option.value = name;
option.innerHTML = name; option.innerHTML = name;
} }
addToselect('#primaryTTSService'); addToselect('#primaryTTSService');
addToselect('#secondaryTTSService'); addToselect('#secondaryTTSService');
}
function determineTootlTipPosition(element) {
const horizontal = document.body.clientWidth / 2;
const vertical = document.body.clientHeight / 2;
element.tip.style.left = `${element.mouse.x}px`;
element.tip.style.top = `${element.mouse.y}px`;
const tipPosition = element.tip.getBoundingClientRect();
if (element.position.x < horizontal && element.position.y < vertical) {
element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`;
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
}
if (element.position.x < horizontal && element.position.y > vertical) {
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
}
if (element.position.x > horizontal && element.position.y < vertical) {
element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`;
element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`;
}
if (element.position.x > horizontal && element.position.y > vertical) {
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`;
}
element.tip.style.visibility = 'visible';
} }
// Small tooltip // Small tooltip
function addSingleTooltip(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;
tip.classList.add('tooltip'); tip.classList.add('tooltip');
tip.innerText = el.getAttribute('tip'); tip.classList.add('tooltiptext');
if (el.src) { tip.innerText = el.getAttribute('tip');
const image = document.createElement('img'); tip.style.transform = `translate(${el.hasAttribute('tip-left') ? 'calc(-100% - 5px)' : '15px'}, ${
image.src = el.src; el.hasAttribute('tip-top') ? '-100%' : '15px'
tip.appendChild(image); })`;
} body.appendChild(tip);
body.appendChild(tip); element.onmousemove = (e) => {
tip.pointerEvents = 'none'; tip.style.left = `${e.x}px`;
element.onmousemove = e => { tip.style.top = `${e.y}px`;
determineTootlTipPosition({ tip.style.zIndex = 1;
position: element.getBoundingClientRect(), tip.style.visibility = 'visible';
mouse: { x: e.x, y: e.y }, };
tip element.onmouseleave = (e) => {
}); tip.style.visibility = 'hidden';
}; };
element.onmouseleave = e => {
tip.style.visibility = 'hidden';
};
}
Array.from(document.body.querySelectorAll('[tip]')).forEach(el => {
addSingleTooltip(el);
}); });
function showChatMessage(article) { function showChatMessage(article, isUser) {
let body = null; document.querySelector('#chatBox').appendChild(article);
if (article !== undefined) { let usernameHtml;
body = document.getElementById('chatBox'); let msg;
body.appendChild(article); let messages = Array.from(document.body.querySelectorAll('.msg-container'));
}
const messages = document.body.querySelectorAll('.msg-container'); if (isUser) {
usernameHtml = article.querySelector('.username-user');
msg = article.querySelector('.msg-box-user');
} else {
usernameHtml = article.querySelector('.username');
msg = article.querySelector('.msg-box');
}
const lastMessage = messages[messages.length - 1]; var style = getComputedStyle(usernameHtml);
lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' }); var style2 = getComputedStyle(usernameHtml);
// const messageId = article.id; const lastMessage = messages[messages.length - 1];
// languageElement.setAttribute('id', article.id); lastMessage.scrollIntoView({ behavior: 'smooth' });
// console.log(article);
const username = article.querySelector('.username').innerHTML;
const image = article.querySelector('.user-img').src;
const statusCircle = article.querySelector('.status-circle').src;
const message = article.querySelector('.msg-box').innerHTML;
const postTime = article.querySelector('.post-time').innerHTML;
socket.emit('chat-out', { messageId, username, image, statusCircle, message, postTime });
} }
function getPostTime() { 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();
const ampm = hours >= 12 ? 'PM' : 'AM'; var 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}`;
return time; return time;
} }
function showPreviewChatMessage() { function showPreviewChatMessage() {
const message = messageTemplates.messageTemplate; const message = messageTemplates.messageTemplate;
document.querySelector('#mini-mid').innerHTML += message; document.querySelector('#mini-mid').innerHTML += message;
const messages = Array.from(document.body.querySelectorAll('#mini-mid')); const messages = Array.from(document.body.querySelectorAll('#mini-mid'));
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
lastMessage.scrollIntoView({ behavior: 'smooth' }); lastMessage.scrollIntoView({ behavior: 'smooth' });
} }
showPreviewChatMessage(); showPreviewChatMessage();
function hideText(button, field) { function hideText(button, field) {
document.body.querySelector(button).addEventListener('click', () => { document.body.querySelector(button).addEventListener('click', () => {
const passwordInput = document.querySelector(field); const passwordInput = document.querySelector(field);
if (passwordInput.type === 'password') { if (passwordInput.type === 'password') {
passwordInput.type = 'lol'; passwordInput.type = 'lol';
} else { } else {
passwordInput.type = 'password'; passwordInput.type = 'password';
} }
}); });
} }
hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN'); hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN');
hideText('.password-toggle-btn2', '#TROVO_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, JSON.stringify(settings));
document.body.querySelector('#ZOOMLEVEL').value = (settings.GENERAL.ZOOMLEVEL * 100).toFixed(0);
}
function getLanguageProperties(languageToDetect) {
try {
const filteredLanguage = Object.keys(languageObject.languages).reduce(function (accumulator, currentValue) {
if (
languageObject.languages[currentValue].IETF === languageToDetect ||
languageObject.languages[currentValue].ISO639 === languageToDetect ||
languageObject.languages[currentValue].ISO3166 === languageToDetect
) {
accumulator[currentValue] = languageObject.languages[currentValue];
}
return accumulator;
}, {});
const language = {
name: Object.getOwnPropertyNames(filteredLanguage)[0],
ISO3166: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO3166,
ISO639: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO639,
IETF: filteredLanguage[Object.keys(filteredLanguage)[0]].IETF
};
return language;
} catch (e) {
// console.error(error);
return 'error';
}
}
let customEmotes = null;
if (fs.existsSync(customEmotesListSavePath)) {
const file = fs.readFileSync(customEmotesListSavePath);
customEmotes = JSON.parse(file);
customEmojiList = [...customEmojiList, ...customEmotes];
}
emojiPicker.customEmoji = customEmojiList;
function saveCustomEmotesToFile(emotes) {
const data = JSON.stringify(emotes);
customEmojiList = [...customEmojiList, data];
const savePath =
main.isPackaged === true ? path.join(resourcesPath, './custom-emotes.json') : path.join(resourcesPath, './config/custom-emotes.json');
fs.writeFile(savePath, data, error => {
if (error) {
console.error(error);
throw error;
}
});
}
function countWords(str) {
return str.trim().split(/\s+/).length;
}
// function saveSettingsToFile(settings) {
// const data = JSON.stringify(settings);
// const savePath =
// main.isPackaged === true ? path.join(resourcesPath, './settings.json') : path.join(resourcesPath, './config/settings.json');
// fs.writeFile(savePath, data, error => {
// if (error) {
// console.error(error);
// throw error;
// }
// });
// }
/*
Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// var videoElement = document.querySelector('video');
// var audioSelect = document.querySelector('select#audioSource');
// var videoSelect = document.querySelector('select#videoSource');
// audioSelect.onchange = getStream;
// videoSelect.onchange = getStream;
// getStream().then(getDevices).then(gotDevices);
// function getDevices() {
// // AFAICT in Safari this only gets default devices until gUM is called :/
// return navigator.mediaDevices.enumerateDevices();
// }
// function gotDevices(deviceInfos) {
// window.deviceInfos = deviceInfos; // make available to console
// console.log('Available input and output devices:', deviceInfos);
// for (const deviceInfo of deviceInfos) {
// const option = document.createElement('option');
// option.value = deviceInfo.deviceId;
// if (deviceInfo.kind === 'audioinput') {
// option.text = deviceInfo.label || `Microphone ${audioSelect.length + 1}`;
// audioSelect.appendChild(option);
// } else if (deviceInfo.kind === 'videoinput') {
// option.text = deviceInfo.label || `Camera ${videoSelect.length + 1}`;
// videoSelect.appendChild(option);
// }
// }
// }
// function getStream() {
// if (window.stream) {
// window.stream.getTracks().forEach(track => {
// track.stop();
// });
// }
// const audioSource = audioSelect.value;
// const videoSource = videoSelect.value;
// const constraints = {
// audio: { deviceId: audioSource ? { exact: audioSource } : undefined },
// video: { deviceId: videoSource ? { exact: videoSource } : undefined }
// };
// return navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
// }
// function gotStream(stream) {
// window.stream = stream; // make stream available to console
// audioSelect.selectedIndex = [...audioSelect.options].findIndex(option => option.text === stream.getAudioTracks()[0].label);
// videoSelect.selectedIndex = [...videoSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
// videoElement.srcObject = stream;
// }
// function handleError(error) {
// console.error('Error: ', error);
// }
// const settingxs = {
// video: true
// };
// navigator.mediaDevices
// .getUserMedia(settingxs)
// .then(stream => {
// const video = document.getElementById('video');
// video.srcObject = stream;
// video.play();
// })
// .catch(err => {
// console.log(err);
// alert('Não há permissões para acessar a webcam.');
// });

View file

@ -1,21 +1,18 @@
function getBotResponse(input) { 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') {
} return 'rock';
if (input === 'scissors') { }
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,5 +1,3 @@
/* global settings */
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const path = require('path'); const path = require('path');
@ -7,115 +5,78 @@ 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);
const requestCount = 0; let requestCount = 0;
function startVtuberModule() { function startVtuberModule() {
if (!settings.MODULES.USE_VTUBER) { if (!settings.MODULES.USE_VTUBER) {
return; return;
} }
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/'))); app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
const vtuber = document.body.querySelector('#BrowsersourceVtuber'); let vtuber = document.body.querySelector('#BrowsersourceVtuber');
const vtuberframe = document.createElement('iframe'); let 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%';
vtuberframe.style.height = '100%'; vtuberframe.style.height = '100%';
vtuberframe.frameBorder = 0; vtuberframe.frameBorder = 0;
vtuber.appendChild(vtuberframe); vtuber.appendChild(vtuberframe);
} }
startVtuberModule(); startVtuberModule();
function startPNGtuberModule() {
if (!settings.MODULES.USE_PNGTUBER) {
return;
}
app.use('/pngtuber', express.static(path.join(__dirname, '../modules/pngtuber/')));
const pngtuber = document.body.querySelector('#BrowsersourcePNGTuber');
const pngtuberframe = document.createElement('iframe');
pngtuberframe.class = 'frame';
pngtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/pngtuber`;
pngtuberframe.style.width = '100%';
pngtuberframe.style.height = '100%';
pngtuberframe.frameBorder = 0;
pngtuber.appendChild(pngtuberframe);
}
startPNGtuberModule();
function startChatWindowModule() {
if (!settings.MODULES.USE_CHATBUBBLE) {
return;
}
app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
const chat = document.body.querySelector('#BrowsersourceChatWindow');
const chatframe = document.createElement('iframe');
chatframe.class = 'frame';
chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`;
chatframe.style.width = '100%';
chatframe.style.height = '100%';
chatframe.frameBorder = 0;
chat.appendChild(chatframe);
}
startChatWindowModule();
function startChatBubbleModule() { function startChatBubbleModule() {
if (!settings.MODULES.USE_CHATBUBBLE) { if (!settings.MODULES.USE_CHATBUBBLE) {
return; return;
} }
app.use('/chatbubble', express.static(path.join(__dirname, '../modules/chatbubble'))); app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
const chatBubble = document.body.querySelector('#BrowsersourceChatBubble'); let chat = document.body.querySelector('#BrowsersourceChat');
const chatBubbleFrame = document.createElement('iframe'); let chatframe = document.createElement('iframe');
chatBubbleFrame.class = 'frame'; chatframe.class = 'frame';
chatBubbleFrame.src = `http://localhost:${settings.GENERAL.PORT}/chatbubble`; chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`;
chatBubbleFrame.style.width = '100%'; chatframe.style.width = '100%';
chatBubbleFrame.style.height = '100%'; chatframe.style.height = '100%';
chatBubbleFrame.frameBorder = 0; chatframe.frameBorder = 0;
chatBubble.appendChild(chatBubbleFrame); chat.appendChild(chatframe);
} }
startChatBubbleModule(); startChatBubbleModule();
function startSTT() {}
// Middleware to conditionally serve routes // Middleware to conditionally serve routes
app.use((req, res, next) => { app.use((req, res, next) => {
if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') { if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') {
res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled
} else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') { } else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') {
res.sendStatus(404); // Return a 404 status for /chat when it's disabled res.sendStatus(404); // Return a 404 status for /chat when it's disabled
} else { } else {
next(); // Proceed to the next middleware or route handler next(); // Proceed to the next middleware or route handler
} }
}); });
localServer.listen(settings.GENERAL.PORT, () => { localServer.listen(settings.GENERAL.PORT, () => {
startVtuberModule(); startVtuberModule();
startChatWindowModule(); 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) => {});
socket.on('chat-out', message => {
socket.broadcast.emit('chat-in', message);
});
// Receive data from the client // Receive data from the client
socket.on('xxx', (logoUrl, username, message) => { socket.on('xxx', (logoUrl, username, message) => {
socket.broadcast.emit('message', logoUrl, username, message); socket.broadcast.emit('message', logoUrl, username, message);
}); });
socket.on('disconnect', () => {}); socket.on('disconnect', () => {});
}); });
module.exports = { startVtuberModule, startChatBubbleModule, startChatWindowModule }; module.exports = { startVtuberModule, startChatBubbleModule };

File diff suppressed because it is too large Load diff

View file

@ -1,143 +1,120 @@
/* global ttsAudioFile, main, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */ let trueMessage = '';
let currentLogoUrl = '';
const voiceSoundArray = []; let currentUsername = '';
let voiceSoundArray = [];
let status = 0; let status = 0;
const counter = 0; let counter = 0;
const playTTS = data => const playTTS = (data) =>
new Promise(resolve => { new Promise((resolve) => {
ttsAudioFile = path.join( ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`);
resourcesPath, const tts = new Audio(ttsAudioFile);
main.isPackaged ? `./sounds/${data.service}_${data.count}.mp3` : `../sounds/${data.service}_${data.count}.mp3` console.log(settings.AUDIO.TTS_AUDIO_DEVICE);
); tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
const tts = new Audio(ttsAudioFile);
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(err); console.error('TEST');
resolve('finished');
return; resolve('finished');
} return;
resolve('finished'); }
}); resolve('finished');
});
});
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
.then(() => {
console.log('playing');
tts.volume = settings.AUDIO.TTS_VOLUME / 100;
tts.play().catch((error) => {
resolve('finished');
});
})
.catch((error) => {
console.error('Failed to set audio output device:', error);
resolve('finished');
});
}); });
tts
.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
.then(() => {
// console.log('playing');
tts.volume = settings.AUDIO.TTS_VOLUME / 100;
tts.play().catch(error => {
if (error) {
console.error(error);
}
resolve('finished');
});
})
.catch(error => {
console.error('Failed to set audio output device:', error);
resolve('finished');
});
});
async function shiftVoice() { async function shiftVoice() {
status = 1; status = 1;
while (voiceSoundArray.length > 0) { while (voiceSoundArray.length > 0) {
await playTTS(voiceSoundArray.shift()); await playTTS(voiceSoundArray.shift());
} }
status = 0; status = 0;
} }
function add(data) { function add(data) {
voiceSoundArray.push(data); voiceSoundArray.push(data);
if (status === 0) { if (status === 0) {
shiftVoice(); shiftVoice();
} }
} }
function playNotificationSound() { function playNotificationSound() {
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
const notfication = new Audio( let notfication = new Audio(
path.join( path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`),
resourcesPath, );
main.isPackaged
? `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`
: `../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().catch(error => { notfication.play();
if (error) { }
console.error(error);
}
});
})
.catch(error => {
console.error('Failed to set audio output device:', error);
});
}
} }
// Play sound function // Play sound function
function playAudio(data) { function playAudio(data) {
if (data.service !== '') { if (data.service !== '') {
add(data); add(data);
} }
} }
async function playVoice(message) { async function playVoice(filteredMessage, logoUrl, username, message) {
if (!settings.TTS.PRIMARY_VOICE) { trueMessage = filteredMessage;
return; currentLogoUrl = logoUrl;
} currentUsername = username;
const textObject = { filtered: message.filteredMessage, formatted: message.formattedMessage }; let textObject = { filtered: filteredMessage, formatted: message };
let voice = settings.TTS.PRIMARY_VOICE; let voice;
textObject.filtered = `${message.username}: ${message.filteredMessage}`; textObject.filtered = `${username}: ${filteredMessage}`;
if ( // if (
settings.LANGUAGE.USE_DETECTION && // settings.TTS.PRIMARY_TTS_LANGUAGE.toLowerCase() !== settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase() &&
settings.TTS.SECONDARY_VOICE && // language[0].lang === settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase()
message.filteredMessage.length > 20 && // ) {
countWords(message.filteredMessage) > 4 // voice = settings.TTS.SECONDARY_TTS_NAME;
) { // textObject.filtered = `${username}: ${filteredMessage}`;
const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE); // } else {
if (message.language.detectedLanguage === null || message.language.detectedLanguage.ISO639 === secondaryTTSLanguage.ISO639) { // voice = settings.TTS.PRIMARY_TTS_NAME;
voice = settings.TTS.SECONDARY_VOICE; // textObject.filtered = `${username}: ${filteredMessage}`;
textObject.filtered = message.originalMessage ? message.originalMessage : message.filteredMessage; // }
const service = document.getElementById('primaryTTSService').value;
switch (service) {
case 'Internal':
const requestData = {
message: textObject.filtered,
voice: settings.TTS.PRIMARY_VOICE,
};
let count = await backend.getInternalTTSAudio(requestData);
playAudio({ service, message: textObject, count });
break;
case 'Amazon':
// playAudio({ service: 'Amazon', message: textObject, count });
break;
case 'Google':
// playAudio({ service: 'Google', message: textObject, count });
break;
} }
}
const service = document.getElementById('primaryTTSService').value; if (settings.MODULES.USE_CHATBUBBLE) {
socket.emit('xxx', currentLogoUrl, currentUsername, textObject);
switch (service) {
case 'Internal': {
const requestData = {
message: textObject.filtered,
voice: voice
};
const count = await backend.getInternalTTSAudio(requestData);
playAudio({ service, message: textObject, count });
break;
} }
case 'Amazon':
// playAudio({ service: 'Amazon', message: textObject, count });
break;
case 'Google':
// playAudio({ service: 'Google', message: textObject, count });
break;
}
if (settings.MODULES.USE_CHATBUBBLE) { playNotificationSound();
socket.emit('xxx', message);
}
} }
module.exports = { playAudio, playVoice, playNotificationSound }; module.exports = { playAudio, playVoice, playNotificationSound };

View file

@ -1,143 +1,145 @@
/* 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;
root.style.setProperty(tempSection, value); root.style.setProperty(tempSection, value);
} }
function setCurrentTheme(adjustTemp = false) { function setCurrentTheme(adjustTemp = false) {
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1'); changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1');
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2'); changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2');
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3'); changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3');
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4'); changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4');
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('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header'); changeColor(
changeColor( '#CHAT_BUBBLE_HEADER',
'#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_HEADER,
settings.THEME.CHAT_BUBBLE_MESSAGE, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header',
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message' );
); changeColor(
'#CHAT_BUBBLE_MESSAGE',
settings.THEME.CHAT_BUBBLE_MESSAGE,
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message',
);
} }
setCurrentTheme(true); setCurrentTheme(true);
function setTheme() { function setTheme() {
if (settings.THEME.USE_CUSTOM_THEME) { if (settings.THEME.USE_CUSTOM_THEME) {
setCurrentTheme(); setCurrentTheme();
} else { } else {
root.style.setProperty('--main-color1', '#6e2c8c'); root.style.setProperty('--main-color1', '#6e2c8c');
root.style.setProperty('--main-color2', 'white'); root.style.setProperty('--main-color2', 'white');
root.style.setProperty('--main-color3', '#211E1E'); root.style.setProperty('--main-color3', '#211E1E');
root.style.setProperty('--main-color4', '#2f2c34'); root.style.setProperty('--main-color4', '#2f2c34');
root.style.setProperty('--top-bar', '#100B12'); root.style.setProperty('--top-bar', '#100B12');
root.style.setProperty('--mid-section', '#352d3d'); root.style.setProperty('--mid-section', '#352d3d');
root.style.setProperty('--chat-bubble', ' #7A6D7F'); root.style.setProperty('--chat-bubble', ' #7A6D7F');
root.style.setProperty('--chat-bubble-header', '#141414'); root.style.setProperty('--chat-bubble-header', '#141414');
root.style.setProperty('--chat-bubble-message', 'white'); root.style.setProperty('--chat-bubble-message', 'white');
} }
} }
document.body.querySelector('#MAIN_COLOR_1').addEventListener('input', () => { document.body.querySelector('#MAIN_COLOR_1').addEventListener('input', () => {
const x = document.getElementById('MAIN_COLOR_1').value; const x = document.getElementById('MAIN_COLOR_1').value;
root.style.setProperty('--main-color1-temp', x); root.style.setProperty('--main-color1-temp', x);
console.log(x); console.log(x);
}); });
document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => { document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => {
settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value; settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1'); changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1');
}); });
document.body.querySelector('#MAIN_COLOR_2').addEventListener('input', () => { document.body.querySelector('#MAIN_COLOR_2').addEventListener('input', () => {
const x = document.getElementById('MAIN_COLOR_2').value; const x = document.getElementById('MAIN_COLOR_2').value;
root.style.setProperty('--main-color2-temp', x); root.style.setProperty('--main-color2-temp', x);
}); });
document.body.querySelector('#MAIN_COLOR_2').addEventListener('change', () => { document.body.querySelector('#MAIN_COLOR_2').addEventListener('change', () => {
settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value; settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2'); changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2');
}); });
document.body.querySelector('#MAIN_COLOR_3').addEventListener('input', () => { document.body.querySelector('#MAIN_COLOR_3').addEventListener('input', () => {
const x = document.getElementById('MAIN_COLOR_3').value; const x = document.getElementById('MAIN_COLOR_3').value;
root.style.setProperty('--main-color3-temp', x); root.style.setProperty('--main-color3-temp', x);
}); });
document.body.querySelector('#MAIN_COLOR_3').addEventListener('change', () => { document.body.querySelector('#MAIN_COLOR_3').addEventListener('change', () => {
settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value; settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3'); changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3');
}); });
document.body.querySelector('#MAIN_COLOR_4').addEventListener('input', () => { document.body.querySelector('#MAIN_COLOR_4').addEventListener('input', () => {
const x = document.getElementById('MAIN_COLOR_4').value; const x = document.getElementById('MAIN_COLOR_4').value;
root.style.setProperty('--main-color4-temp', x); root.style.setProperty('--main-color4-temp', x);
}); });
document.body.querySelector('#MAIN_COLOR_4').addEventListener('change', () => { document.body.querySelector('#MAIN_COLOR_4').addEventListener('change', () => {
settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value; settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4'); changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4');
}); });
document.body.querySelector('#TOP_BAR').addEventListener('input', () => { document.body.querySelector('#TOP_BAR').addEventListener('input', () => {
const x = document.getElementById('TOP_BAR').value; const x = document.getElementById('TOP_BAR').value;
root.style.setProperty('--top-bar-temp', x); root.style.setProperty('--top-bar-temp', x);
}); });
document.body.querySelector('#TOP_BAR').addEventListener('change', () => { document.body.querySelector('#TOP_BAR').addEventListener('change', () => {
settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value; settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar'); changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar');
}); });
document.body.querySelector('#MID_SECTION').addEventListener('input', () => { document.body.querySelector('#MID_SECTION').addEventListener('input', () => {
const x = document.getElementById('MID_SECTION').value; const x = document.getElementById('MID_SECTION').value;
root.style.setProperty('--mid-section-temp', x); root.style.setProperty('--mid-section-temp', x);
}); });
document.body.querySelector('#MID_SECTION').addEventListener('change', () => { document.body.querySelector('#MID_SECTION').addEventListener('change', () => {
settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value; settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section'); changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section');
}); });
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('input', () => { document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('input', () => {
const x = document.getElementById('CHAT_BUBBLE_BG').value; const x = document.getElementById('CHAT_BUBBLE_BG').value;
root.style.setProperty('--chat-bubble-temp', x); root.style.setProperty('--chat-bubble-temp', x);
}); });
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('change', () => { document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('change', () => {
settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value; settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble'); changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble');
}); });
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('input', () => { document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('input', () => {
const x = document.getElementById('CHAT_BUBBLE_HEADER').value; const x = document.getElementById('CHAT_BUBBLE_HEADER').value;
root.style.setProperty('--chat-bubble-header-temp', x); root.style.setProperty('--chat-bubble-header-temp', x);
}); });
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('change', () => { document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('change', () => {
settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value; settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header'); changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header');
}); });
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('input', () => { document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('input', () => {
const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value; const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
root.style.setProperty('--chat-bubble-message-temp', x); root.style.setProperty('--chat-bubble-message-temp', x);
}); });
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('change', () => { document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('change', () => {
settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value; settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message'); changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message');
}); });
module.exports = { setTheme }; module.exports = { setTheme };

View file

@ -1,392 +0,0 @@
let __assign =
(this && this.__assign) ||
function () {
__assign =
Object.assign ||
function (t) {
for (let s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (const p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
let tokens = [];
const TokenAutocomplete = /** @class */ (function () {
function TokenAutocomplete(options) {
this.KEY_BACKSPACE = 8;
this.KEY_ENTER = 13;
this.KEY_UP = 38;
this.KEY_DOWN = 40;
this.tokens = tokens;
this.defaults = {
name: '',
selector: '',
noMatchesText: null,
initialTokens: null,
initialSuggestions: null,
suggestionsUri: '',
suggestionRenderer: TokenAutocomplete.Autocomplete.defaultRenderer,
minCharactersForSuggestion: 1
};
this.options = __assign(__assign({}, this.defaults), options);
const passedContainer = document.querySelector(this.options.selector);
if (!passedContainer) {
throw new Error('passed selector does not point to a DOM element.');
}
this.container = passedContainer;
this.container.classList.add('token-autocomplete-container');
if (!Array.isArray(this.options.initialTokens) && !Array.isArray(this.options.initialSuggestions)) {
this.parseTokensAndSuggestions();
}
this.hiddenSelect = document.createElement('select');
this.hiddenSelect.id = this.container.id + '-select';
this.hiddenSelect.name = this.options.name;
this.hiddenSelect.setAttribute('multiple', 'true');
this.hiddenSelect.style.display = 'none';
this.textInput = document.createElement('span');
this.textInput.id = this.container.id + '-input';
this.textInput.classList.add('token-autocomplete-input');
this.textInput.setAttribute('data-placeholder', 'Enter BetterTTV channel');
this.textInput.contentEditable = 'true';
this.container.appendChild(this.textInput);
this.container.appendChild(this.hiddenSelect);
this.select = new TokenAutocomplete.MultiSelect(this);
this.autocomplete = new TokenAutocomplete.Autocomplete(this);
this.debug(false);
const me = this;
if (Array.isArray(this.options.initialTokens)) {
this.options.initialTokens.forEach(function (token) {
if (typeof token === 'object') {
me.select.addToken(token.value, token.text);
}
});
}
this.textInput.addEventListener('keydown', function (event) {
if (event.which === me.KEY_ENTER || event.key === me.KEY_ENTER) {
event.preventDefault();
const highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
if (highlightedSuggestion !== null) {
if (highlightedSuggestion.classList.contains('token-autocomplete-suggestion-active')) {
me.select.removeTokenWithText(highlightedSuggestion.textContent);
} else {
me.select.addToken(highlightedSuggestion.getAttribute('data-value'), highlightedSuggestion.textContent);
}
} else {
me.select.addToken(me.textInput.textContent, me.textInput.textContent);
}
me.clearCurrentInput();
} else if (me.textInput.textContent === '' && (event.which === me.KEY_BACKSPACE || event.key === me.KEY_BACKSPACE)) {
event.preventDefault();
// me.select.removeLastToken();
}
});
this.textInput.addEventListener('keyup', function (event) {
let _a, _b;
if ((event.which === me.KEY_UP || event.key === me.KEY_UP) && me.autocomplete.suggestions.childNodes.length > 0) {
const highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
const aboveSuggestion = (_a = highlightedSuggestion) === null || _a === void 0 ? void 0 : _a.previousSibling;
if (aboveSuggestion != null) {
me.autocomplete.highlightSuggestion(aboveSuggestion);
}
return;
}
if ((event.which === me.KEY_DOWN || event.key === me.KEY_DOWN) && me.autocomplete.suggestions.childNodes.length > 0) {
const highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
const belowSuggestion = (_b = highlightedSuggestion) === null || _b === void 0 ? void 0 : _b.nextSibling;
if (belowSuggestion != null) {
me.autocomplete.highlightSuggestion(belowSuggestion);
}
return;
}
me.autocomplete.hideSuggestions();
me.autocomplete.clearSuggestions();
const value = me.textInput.textContent || '';
if (value.length >= me.options.minCharactersForSuggestion) {
if (Array.isArray(me.options.initialSuggestions)) {
me.options.initialSuggestions.forEach(function (suggestion) {
if (typeof suggestion !== 'object') {
// the suggestion is of wrong type and therefore ignored
return;
}
if (value.localeCompare(suggestion.text.slice(0, value.length), undefined, { sensitivity: 'base' }) === 0) {
// The suggestion starts with the query text the user entered and will be displayed
me.autocomplete.addSuggestion(suggestion);
}
});
if (me.autocomplete.suggestions.childNodes.length > 0) {
me.autocomplete.highlightSuggestionAtPosition(0);
} else if (me.options.noMatchesText) {
me.autocomplete.addSuggestion({ value: '_no_match_', text: me.options.noMatchesText, description: null });
}
} else if (me.options.suggestionsUri.length > 0) {
me.autocomplete.requestSuggestions(value);
}
}
});
this.container.tokenAutocomplete = this;
}
/**
* Searches the element given as a container for option elements and creates active tokens (when the option is marked selected)
* and suggestions (all options found) from these. During this all found options are removed from the DOM.
*/
TokenAutocomplete.prototype.parseTokensAndSuggestions = function () {
const initialTokens = [];
const initialSuggestions = [];
const options = this.container.querySelectorAll('option');
const me = this;
options.forEach(function (option) {
if (option.text != null) {
if (option.hasAttribute('selected')) {
initialTokens.push({ value: option.value, text: option.text });
}
initialSuggestions.push({ value: option.value, text: option.text, description: null });
}
me.container.removeChild(option);
});
if (initialTokens.length > 0) {
this.options.initialTokens = initialTokens;
}
if (initialSuggestions.length > 0) {
this.options.initialSuggestions = initialSuggestions;
}
};
/**
* Clears the currently present tokens and creates new ones from the given input value.
*
* @param {(Array\|string)} value - either the name of a single token or a list of tokens to create
*/
TokenAutocomplete.prototype.val = function (value) {
this.select.clear();
if (Array.isArray(value)) {
const me1 = this;
value.forEach(function (token) {
if (typeof token === 'object') {
me1.select.addToken(token.value, token.text);
}
});
} else {
this.select.addToken(value.value, value.text);
}
};
TokenAutocomplete.prototype.clearCurrentInput = function () {
this.textInput.textContent = '';
};
TokenAutocomplete.prototype.debug = function (state) {
if (state) {
this.log = console.log.bind(window.console);
} else {
this.log = function () {};
}
};
let _a;
TokenAutocomplete.MultiSelect = /** @class */ (function () {
function saveTokens() {
settings.TWITCH.BETTERTTV_CHANNELS = tokens;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
}
function class1(parent) {
this.parent = parent;
this.container = parent.container;
this.options = parent.options;
}
/**
* Adds a token with the specified name to the list of currently prensent tokens displayed to the user and the hidden select.
*
* @param {string} tokenText - the name of the token to create
*/
class1.prototype.addToken = function (tokenValue, tokenText) {
if (tokenValue === null || tokenText === null) {
return;
}
tokens.push({ value: tokenValue, text: tokenText });
saveTokens();
const option = document.createElement('option');
option.text = tokenText;
option.value = tokenValue;
option.setAttribute('selected', 'true');
option.setAttribute('data-text', tokenText);
option.setAttribute('data-value', tokenValue);
this.parent.hiddenSelect.add(option);
const token = document.createElement('span');
token.classList.add('token-autocomplete-token');
token.setAttribute('data-text', tokenText);
option.setAttribute('data-value', tokenValue);
token.textContent = tokenText;
const deleteToken = document.createElement('span');
deleteToken.classList.add('token-autocomplete-token-delete');
deleteToken.textContent = '\u00D7';
token.appendChild(deleteToken);
const me = this;
deleteToken.addEventListener('click', function (event) {
me.removeToken(token);
});
this.container.insertBefore(token, this.parent.textInput);
this.parent.log('added token', token);
};
/**
* Completely clears the currently present tokens from the field.
*/
class1.prototype.clear = function () {
const tokens = this.container.querySelectorAll('.token-autocomplete-token');
const me = this;
tokens.forEach(function (token) {
me.removeToken(token);
});
};
/**
* Removes the last token in the list of currently present token. This is the last added token next to the input field.
*/
class1.prototype.removeLastToken = function () {
const tokens = this.container.querySelectorAll('.token-autocomplete-token');
const token = tokens[tokens.length - 1];
if (tokens.length !== 0) {
this.removeToken(token);
}
};
/**
* Removes the specified token from the list of currently present tokens.
*
* @param {Element} token - the token to remove
*/
class1.prototype.removeToken = function (token) {
let _a, _b;
this.container.removeChild(token);
const tokenText = token.getAttribute('data-text');
const hiddenOption = this.parent.hiddenSelect.querySelector('option[data-text="' + tokenText + '"]');
(_b = (_a = hiddenOption) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0
? void 0
: _b.removeChild(hiddenOption);
this.parent.log('removed token', token.textContent);
tokens.splice(
tokens.findIndex(t => t.value === token.value),
1
);
saveTokens();
};
class1.prototype.removeTokenWithText = function (tokenText) {
if (tokenText === null) {
return;
}
const token = this.container.querySelector('.token-autocomplete-token[data-text="' + tokenText + '"]');
if (token !== null) {
this.removeToken(token);
}
};
return class1;
})();
TokenAutocomplete.Autocomplete =
((_a = /** @class */ (function () {
function class2(parent) {
this.parent = parent;
this.container = parent.container;
this.options = parent.options;
this.renderer = parent.options.suggestionRenderer;
this.suggestions = document.createElement('ul');
this.suggestions.id = this.container.id + '-suggestions';
this.suggestions.classList.add('token-autocomplete-suggestions');
this.container.appendChild(this.suggestions);
}
/**
* Hides the suggestions dropdown from the user.
*/
class2.prototype.hideSuggestions = function () {
this.suggestions.style.display = '';
};
/**
* Shows the suggestions dropdown to the user.
*/
class2.prototype.showSuggestions = function () {
this.suggestions.style.display = 'block';
};
class2.prototype.highlightSuggestionAtPosition = function (index) {
const suggestions = this.suggestions.querySelectorAll('li');
suggestions.forEach(function (suggestion) {
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
});
suggestions[index].classList.add('token-autocomplete-suggestion-highlighted');
};
class2.prototype.highlightSuggestion = function (suggestion) {
this.suggestions.querySelectorAll('li').forEach(function (suggestion) {
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
});
suggestion.classList.add('token-autocomplete-suggestion-highlighted');
};
/**
* Removes all previous suggestions from the dropdown.
*/
class2.prototype.clearSuggestions = function () {
this.suggestions.innerHTML = '';
};
/**
* Loads suggestions matching the given query from the rest service behind the URI given as an option while initializing the field.
*
* @param query the query to search suggestions for
*/
class2.prototype.requestSuggestions = function (query) {
const me = this;
const request = new XMLHttpRequest();
request.onload = function () {
if (Array.isArray(request.response)) {
request.response.forEach(function (suggestion) {
me.addSuggestion(suggestion);
});
}
};
request.open('GET', me.options.suggestionsUri + '?query=' + query, true);
request.responseType = 'json';
request.setRequestHeader('Content-type', 'application/json');
request.send();
};
/**
* Adds a suggestion with the given text matching the users input to the dropdown.
*
* @param {string} suggestionText - the text that should be displayed for the added suggestion
*/
class2.prototype.addSuggestion = function (suggestion) {
const element = this.renderer(suggestion);
element.setAttribute('data-value', suggestion.value);
const me = this;
element.addEventListener('click', function (_event) {
if (suggestion.text === me.options.noMatchesText) {
return true;
}
if (element.classList.contains('token-autocomplete-suggestion-active')) {
me.parent.select.removeTokenWithText(suggestion.text);
} else {
me.parent.select.addToken(suggestion.value, suggestion.text);
}
me.clearSuggestions();
me.hideSuggestions();
me.parent.clearCurrentInput();
});
if (this.container.querySelector('.token-autocomplete-token[data-text="' + suggestion.text + '"]') !== null) {
element.classList.add('token-autocomplete-suggestion-active');
}
this.suggestions.appendChild(element);
this.showSuggestions();
me.parent.log('added suggestion', suggestion);
};
return class2;
})()),
(_a.defaultRenderer = function (suggestion) {
const option = document.createElement('li');
option.textContent = suggestion.text;
if (suggestion.description) {
const description = document.createElement('small');
description.textContent = suggestion.description;
description.classList.add('token-autocomplete-suggestion-description');
option.appendChild(description);
}
return option;
}),
_a);
return TokenAutocomplete;
})();
module.exports = { TokenAutocomplete };

View file

@ -1,344 +0,0 @@
/* global client,axios,customEmojiList, saveCustomEmotesToFile,startTime,Sockette, playNotificationSound, chat, replaceChatMessageWithCustomEmojis, messageId, addSingleTooltip, settingsPath, fs, ini, backend, main, path, resourcesPath, customEmojis, emojiPicker,config, settings, options, sound, showChatMessage, messageTemplates, getPostTime */
function setTrovoSendButton() {
const languageSelectContent = document.querySelector('.send-to-channel');
const option = document.createElement('div');
option.classList = 'language-select';
const checkbox = document.createElement('input');
checkbox.classList = 'checkbox';
checkbox.type = 'checkbox';
checkbox.id = 'SEND_CHAT_TROVO';
option.appendChild(checkbox);
const label = document.createElement('label');
label.classList = 'toggle-small';
option.setAttribute('for', 'SEND_CHAT_TROVO');
checkbox.checked = settings.TROVO.SEND_CHAT;
option.appendChild(label);
const network = document.createElement('img');
network.src = './images/trovo.png';
network.classList = 'emote';
option.appendChild(network);
option.addEventListener('click', () => {
checkbox.checked = !checkbox.checked;
settings.TROVO.SEND_CHAT = checkbox.checked;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
});
languageSelectContent.appendChild(option);
}
setTrovoSendButton();
function getTrovoUserId() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://open-api.trovo.live/openplatform/validate',
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID,
Authorization: `OAuth ${settings.TROVO.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(response => {
settings.TROVO.USERNAME = response.data.nick_name;
settings.TROVO.USER_ID = response.data.uid;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
getTrovoUserInfo();
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
function getTrovoUserInfo() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://open-api.trovo.live/openplatform/getuserinfo',
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID,
Authorization: `OAuth ${settings.TROVO.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(response => {
settings.TROVO.USER_LOGO_URL = response.data.profilePic;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
function saveTrovoEmotesToFile(TrovoEmotes) {
const data = JSON.stringify(TrovoEmotes);
const savePath =
main.isPackaged === true ? path.join(resourcesPath, './custom-emotes.json') : path.join(resourcesPath, './config/custom-emotes.json');
fs.writeFile(savePath, data, error => {
if (error) {
console.error(error);
throw error;
}
});
}
function formatTrovoEmotes(data) {
if (data.channels.customizedEmotes.channel) {
data.channels.customizedEmotes.channel.forEach(channel => {
channel.emotes.forEach(emote => {
const emojiToBeAdded = {
name: ':' + emote.name,
shortcodes: [':' + emote.name],
url: emote.url,
category: 'Trovo ' + settings.TROVO.CHANNEL_NAME
};
customEmojis.push(emojiToBeAdded);
});
});
}
// data.channels.eventEmotes.forEach(emote => {
// const emojiToBeAdded = {
// name: emote.name,
// shortcodes: [emote.name],
// url: emote.url,
// category: 'Trovo event emotes'
// };
// customEmojis.push(emojiToBeAdded);
// });
if (data.channels.globalEmotes) {
data.channels.globalEmotes.forEach(emote => {
const emojiToBeAdded = {
name: ':' + emote.name,
shortcodes: [':' + emote.name],
url: emote.url,
category: 'Trovo Global'
};
customEmojis.push(emojiToBeAdded);
});
}
customEmojiList = [...customEmojis];
emojiPicker.customEmoji = customEmojiList;
saveCustomEmotesToFile(customEmojis);
}
function getTrovoEmotes() {
// Get user Logo with access token
options = {
method: 'POST',
url: 'https://open-api.trovo.live/openplatform/getemotes',
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID,
Authorization: `OAuth ${settings.TROVO.OAUTH_TOKEN}`
},
data: {
emote_type: 0,
channel_id: [settings.TROVO.CHANNEL_ID]
}
};
axios
.request(options)
.then(response => {
formatTrovoEmotes(response.data);
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
function getTrovoChannelId() {
options = {
method: 'POST',
url: 'https://open-api.trovo.live/openplatform/getusers',
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID
},
data: { user: [settings.TROVO.CHANNEL_NAME.toLowerCase()] }
};
axios
.request(options)
.then(response => {
settings.TROVO.CHANNEL_ID = response.data.users[0].channel_id;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
getTrovoEmotes();
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
function sendTrovoMessage(message) {
options = {
method: 'POST',
url: 'https://open-api.trovo.live/openplatform/chat/send',
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID,
Authorization: `OAuth ${settings.TROVO.OAUTH_TOKEN}`
},
data: { content: message, channel_id: settings.TROVO.CHANNEL_ID }
};
axios
.request(options)
.then(response => {
console.log(response);
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
const getTrovoChatToken = () =>
new Promise(resolve => {
options = {
method: 'GET',
url: `https://open-api.trovo.live/openplatform/chat/channel-token/${settings.TROVO.CHANNEL_ID}`,
headers: {
Accept: 'application/json',
'Client-ID': settings.TROVO.CLIENT_ID
}
};
axios
.request(options)
.then(response => {
resolve(response.data.token);
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
});
async function displayTrovoMessage(message) {
messageId++;
const article = document.createElement('article');
article.className = 'msg-container sender';
article.setAttribute('id', messageId);
article.innerHTML = messageTemplates.trovoTemplate;
const userImg = article.querySelector('.user-img');
if (userImg) {
userImg.src = message.avatar;
userImg.setAttribute('tip', '');
}
addSingleTooltip(userImg);
const usernameHtml = article.querySelector('.username');
if (usernameHtml) {
usernameHtml.innerText = message.nick_name;
}
const postTime = article.querySelector('.post-time');
if (postTime) {
postTime.innerText = getPostTime();
}
article.appendChild(postTime);
const formattedMessage = article.querySelector('.msg-box');
if (formattedMessage) {
formattedMessage.innerHTML = message.content;
}
await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => {
formattedMessage.innerHTML = data;
showChatMessage(article);
});
}
async function connectToTrovoChat(token) {
let startTime = new Date();
let heartbeat = null;
const wsTrovo = new Sockette('wss://open-chat.trovo.live/chat', {
onopen: e => {
// console.log('Connected!', e);
wsTrovo.json({ type: 'AUTH', nonce: 'loquendoBot', data: { token } });
},
onmessage: e => {
// console.log('Received:', e);
const data = JSON.parse(e.data);
// console.log(data);
if (data.type === 'RESPONSE' && !data.error) {
wsTrovo.json({ type: 'PING', nonce: 'loquendoBot' });
heartbeat = setInterval(() => {
wsTrovo.json({ type: 'PING', nonce: 'loquendoBot' });
}, 30e3);
}
if (data.type === 'CHAT' && data.data.chats) {
data.data.chats.forEach(message => {
// console.log(message);
if (message.type === 5007) {
return;
}
if (Math.round(startTime.getTime() / 1000) < message.send_time) {
displayTrovoMessage({
nick_name: message.nick_name,
avatar: message.avatar,
content: message.content
});
}
});
}
if (data.type === 'PONG') {
clearInterval(heartbeat);
heartbeat = setInterval(() => {
wsTrovo.json({ type: 'PING', nonce: 'loquendoBot' });
}, data.data.gap * 1000);
}
},
onreconnect: e => {
console.log('Reconnecting...', e);
startTime = new Date();
clearInterval(heartbeat);
getTrovoChatToken().then(data => {
token = data;
});
},
onmaximum: e => console.log('Stop Attempting!', e),
onclose: e => console.log('Closed!', e),
onerror: e => console.log('Error:', e)
});
// ws.close(); // graceful shutdown
}
if (settings.TROVO.CHANNEL_ID) {
getTrovoChatToken().then(data => {
connectToTrovoChat(data);
});
}
module.exports = { getTrovoUserId, getTrovoChannelId, sendTrovoMessage };

View file

@ -1,507 +1,147 @@
/* global client, axios, playNotificationSound, betterTTVAutocomplete, saveCustomEmotesToFile, customEmojiList, chat, replaceChatMessageWithCustomEmojis, messageId, addSingleTooltip, settingsPath, fs, ini, backend, main, path, resourcesPath, customEmojis, emojiPicker,config, settings, options, sound, showChatMessage, messageTemplates, getPostTime */
const tmi = require('tmi.js'); const tmi = require('tmi.js');
const axios = require('axios');
let client = null; let client;
let logoUrl = null;
const twitchChannels = [];
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);
} }
if (settings.TWITCH.USERNAME && settings.TWITCH.OAUTH_TOKEN) { 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);
client.on('message', (channel, tags, message, self) => {
if (self || tags['display-name'] === settings.TWITCH.USERNAME) {
return;
}
const emotes = tags.emotes || {};
const emoteValues = Object.entries(emotes);
let filteredMessage = message;
let emoteMessage = message;
emoteValues.forEach(entry => {
entry[1].forEach(lol => {
const [start, end] = lol.split('-');
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);
filteredMessage = filteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
});
});
const messageObject = parseString(emoteMessage);
getProfileImage(tags['user-id'], tags['display-name'], messageObject, filteredMessage);
});
}
function checkIfTokenIsValid() {
options = {
method: 'GET',
url: 'https://id.twitch.tv/oauth2/validate',
headers: {
Authorization: `OAuth ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(response => {
document.getElementById('TWITCH_CHANNEL_NAME').disabled = false;
})
.catch(error => {
console.error(error);
document.getElementById('TWITCH_CHANNEL_NAME').disabled = true;
config.createNotification('Oauth Token is invalid, please obtain a new one', 'error');
});
}
setInterval(checkIfTokenIsValid, 600000);
if (settings.TWITCH.OAUTH_TOKEN) {
checkIfTokenIsValid();
} else {
document.getElementById('TWITCH_CHANNEL_NAME').disabled = true;
}
function ping(element) { function ping(element) {
const value = document.body.querySelector(element); let 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!';
}); });
} }
async function displayTwitchMessage(logoUrl, username, messageObject, filteredMessage) { function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage) {
messageId++; const article = document.createElement('article');
const article = document.createElement('article'); article.className = 'msg-container msg-remote';
article.className = 'msg-container sender';
article.setAttribute('id', messageId);
article.innerHTML = messageTemplates.twitchTemplate; article.innerHTML = messageTemplates.twitchTemplate;
const userImg = article.querySelector('.user-img');
if (userImg) {
userImg.src = logoUrl;
userImg.setAttribute('tip', '');
}
addSingleTooltip(userImg);
const usernameHtml = article.querySelector('.username'); const userImg = article.querySelector('.icon-container > .user-img');
if (usernameHtml) { if (userImg) {
usernameHtml.innerText = username; userImg.src = logoUrl;
}
const postTime = article.querySelector('.post-time');
if (postTime) {
postTime.innerText = getPostTime();
}
article.appendChild(postTime);
const formattedMessage = article.querySelector('.msg-box');
if (formattedMessage) {
messageObject.forEach(entry => {
if (entry.text) {
formattedMessage.innerHTML += entry.text;
} else {
formattedMessage.innerHTML += entry.html;
}
});
}
await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => {
formattedMessage.innerHTML = data;
showChatMessage(article);
});
if (settings.LANGUAGE.USE_DETECTION && filteredMessage.length > 20 && countWords(filteredMessage) > 4) {
await backend.getDetectedLanguage({ message: filteredMessage, messageId, username, logoUrl, formattedMessage }).then(language => {
const languageElement = document.createElement('span');
languageElement.classList = `fi fi-${language.ISO3166} fis language-icon flag-icon`;
languageElement.setAttribute('tip', language.name);
article.appendChild(languageElement);
addSingleTooltip(languageElement);
if (filteredMessage && !settings.LANGUAGE.OUTPUT_TO_TTS) {
sound.playVoice({
filteredMessage,
logoUrl,
username,
formattedMessage,
language: { selectedLanguage: null, detectedLanguage: language }
});
}
// window.article = article;
});
} else {
if (filteredMessage) {
sound.playVoice({ filteredMessage, logoUrl, username, formattedMessage });
} }
// window.article = article; const usernameHtml = article.querySelector('.username');
} if (usernameHtml) {
usernameHtml.innerText = username;
}
sound.playNotificationSound(); const postTime = document.createElement('span');
postTime.classList.add('post-time');
if (postTime) {
postTime.innerText = getPostTime();
}
const iconContainer = article.querySelector('.icon-container');
iconContainer.appendChild(postTime);
const msg = article.querySelector('.msg-box');
if (msg) {
messageObject.forEach((entry) => {
if (entry.text) {
msg.innerHTML += entry.text;
} else {
msg.innerHTML += entry.html;
}
});
// msg.appendChild(postTime);
}
// Appends the message to the main chat box (shows the message)
showChatMessage(article, false);
if (fileteredMessage) {
sound.playVoice(fileteredMessage, logoUrl, username, msg);
}
window.article = article;
} }
function getProfileImage(userid, username, message, filteredMessage) { function getProfileImage(userid, username, message, fileteredMessage) {
// 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?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) => {
logoUrl = responseLogoUrl.data.data[0].profile_image_url; const logoUrl = responseLogoUrl.data.data[0].profile_image_url;
displayTwitchMessage(logoUrl, username, message, filteredMessage); displayTwitchMessage(logoUrl, username, message, fileteredMessage);
}) })
.catch(error => { .catch((error) => {
console.error(error); console.error(error);
}); });
} }
function parseString(inputString) { function parseString(inputString) {
const regex = /(<img.*?\/>)|([^<]+)/g; const regex = /(<img.*?\/>)|([^<]+)/g;
const matches = inputString.match(regex) || []; const matches = inputString.match(regex) || [];
const result = []; const result = [];
for (let i = 0; i < matches.length; i++) { for (let i = 0; i < matches.length; i++) {
const match = matches[i].trim(); const match = matches[i].trim();
if (match.startsWith('<img')) { if (match.startsWith('<img')) {
result.push({ html: match }); result.push({ html: match });
} }
if (match !== '' && !match.startsWith('<img')) { if (match !== '' && !match.startsWith('<img')) {
result.push({ text: match }); result.push({ text: match });
}
}
return result;
}
function formatTwitchEmotes(channel) {
if (channel.emotes.length === 0) {
return;
}
channel.emotes.forEach(emote => {
if (channel.name !== 'Twitch Global' && emote.emote_type === 'bitstier') {
return;
}
if (channel.name !== 'Twitch Global' && emote.emote_type === 'subscriptions' && parseInt(channel.tier) < parseInt(emote.tier)) {
return;
}
if (channel.name !== 'Twitch Global' && emote.emote_type === 'follower ' && parseInt(channel.tier) === 0) {
return;
}
const emojiToBeAdded = {
name: emote.name,
shortcodes: [emote.name],
url: emote.images.url_1x,
category: 'Twitch ' + channel.broadcaster_name
};
customEmojis.push(emojiToBeAdded);
});
customEmojiList = [...customEmojis];
emojiPicker.customEmoji = customEmojiList;
saveCustomEmotesToFile(customEmojis);
}
function formatBetterTtvEmotes(data) {
if (data.emotes.length === 0) {
return;
}
data.emotes.forEach(emote => {
const emojiToBeAdded = {
name: emote.code,
shortcodes: [emote.code],
url: `https://cdn.betterttv.net/emote/${emote.id}/1x.webp`,
category: data.name
};
customEmojis.push(emojiToBeAdded);
});
customEmojiList = [...customEmojis];
emojiPicker.customEmoji = customEmojiList;
saveCustomEmotesToFile(customEmojis);
}
function getBetterTtvGLobalEmotes() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://api.betterttv.net/3/cached/emotes/global',
headers: {}
};
axios
.request(options)
.then(response => {
formatBetterTtvEmotes({ name: 'BetterTTV Global', emotes: response.data });
})
.catch(error => {
console.error(error);
});
}
function getBetterTtvChannelsEmotes() {
betterTTVAutocomplete.tokens.forEach(channel => {
getTwitchChannelId(channel.value).then(channelId => {
getBetterTtvChannelEmotes(channelId);
});
});
}
function getBetterTtvChannelEmotes(channel) {
// Get user Logo with access token
options = {
method: 'GET',
url: `https://api.betterttv.net/3/cached/users/twitch/${channel}`,
headers: {}
};
axios
.request(options)
.then(response => {
formatBetterTtvEmotes({ name: 'BetterTTV Channels', emotes: [...response.data.channelEmotes, ...response.data.sharedEmotes] });
})
.catch(error => {
console.error(error);
});
}
function getTwitchUserFollows(paginationToken) {
let url = '';
if (!paginationToken) {
url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&first=100`;
} else {
url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&after=${paginationToken}`;
}
options = {
method: 'GET',
url: url,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
// console.log(responseLogoUrl);
responseLogoUrl.data.data.forEach(channel => {
twitchChannels.push({
broadcaster_id: channel.broadcaster_id,
broadcaster_name: channel.broadcaster_name,
tier: '0'
});
});
if (Object.keys(responseLogoUrl.data.pagination).length !== 0) {
getTwitchUserFollows(responseLogoUrl.data.pagination.cursor);
} else {
getTwitchChannelSubscriptions(twitchChannels);
}
})
.catch(error => {
console.error(error);
});
}
function getTwitchChannelSubscriptions(channels) {
channels.forEach(channel => {
options = {
method: 'GET',
url: `https://api.twitch.tv/helix/subscriptions/user?broadcaster_id=${channel.broadcaster_id}&user_id=${settings.TWITCH.USER_ID}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
const objIndex = twitchChannels.findIndex(obj => obj.broadcaster_id === channel.broadcaster_id);
twitchChannels[objIndex].tier = responseLogoUrl.data.data[0].tier;
getTwitchChannelEmotes(channel);
})
.catch(error => {
if (error.response.status !== 404) {
console.error(error);
} }
});
});
}
function getTwitchChannelEmotes(channel) {
// Get user Logo with access token
options = {
method: 'GET',
url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channel.broadcaster_id}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
} }
}; return result;
axios
.request(options)
.then(responseLogoUrl => {
if (responseLogoUrl.data.data.length !== 0) {
channel.emotes = responseLogoUrl.data.data;
formatTwitchEmotes(channel);
}
})
.catch(error => {
console.error(error);
});
} }
function getTwitchGlobalEmotes() { client.on('message', (channel, tags, message, self) => {
// Get user Logo with access token if (self) {
options = { return;
method: 'GET',
url: 'https://api.twitch.tv/helix/chat/emotes/global',
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
} }
}; const emotes = tags.emotes || {};
const emoteValues = Object.entries(emotes);
let fileteredMessage = message;
let emoteMessage = message;
axios emoteValues.forEach((entry) => {
.request(options) entry[1].forEach((lol) => {
.then(responseLogoUrl => { const [start, end] = lol.split('-');
formatTwitchEmotes({ broadcaster_name: 'Global', emotes: responseLogoUrl.data.data }); let 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);
.catch(error => { fileteredMessage = fileteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
console.error(error); });
}); });
}
async function getUserAvailableTwitchEmotes() { let messageObject = parseString(emoteMessage);
if (settings.TWITCH.OAUTH_TOKEN) { getProfileImage(tags['user-id'], tags['display-name'], messageObject, fileteredMessage);
await getTwitchGlobalEmotes(); });
await getTwitchUserFollows();
await getTwitchChannelEmotes({
broadcaster_id: settings.TWITCH.USER_ID,
broadcaster_name: settings.TWITCH.USERNAME,
tier: '3000'
});
await getTwitchChannelEmotes({
broadcaster_id: settings.TWITCH.CHANNEL_USER_ID,
broadcaster_name: settings.TWITCH.CHANNEL_NAME,
tier: '1'
});
}
}
const getTwitchChannelId = channelName => module.exports = { sendMessage, ping, client };
new Promise(resolve => {
options = {
method: 'GET',
url: `https://api.twitch.tv/helix/users?login=${channelName}`,
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(response => {
resolve(response.data.data[0].id);
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain channel info, please try again', 'error');
});
});
function getTwitchUserId() {
// Get user Logo with access token
options = {
method: 'GET',
url: 'https://api.twitch.tv/helix/users',
headers: {
'Client-ID': settings.TWITCH.CLIENT_ID,
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
}
};
axios
.request(options)
.then(responseLogoUrl => {
// console.log(responseLogoUrl.data.data[0]);
settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name;
settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url;
settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
config.createNotification('Obtained user info succesfully', 'success');
})
.catch(error => {
console.error(error);
config.createNotification('could not obtain user info, please try again', 'error');
});
}
// const Sockette = require('sockette');
// const ws = new Sockette('wss://eventsub.wss.twitch.tv/ws', {
// timeout: 5e3,
// maxAttempts: 10,
// onopen: e => console.log('Connected!', e),
// onmessage: e => console.log('Received:', e),
// onreconnect: e => console.log('Reconnecting...', e),
// onmaximum: e => console.log('Stop Attempting!', e),
// onclose: e => console.log('Closed!', e),
// onerror: e => console.log('Error:', e)
// });
// ws.send('Hello, world!');
// ws.json({ type: 'ping' });
// ws.close(); // graceful shutdown
// Reconnect 10s later
// setTimeout(ws.reconnect, 10e3);
module.exports = {
sendMessage,
ping,
client,
getBetterTtvGLobalEmotes,
getBetterTtvChannelEmotes,
getBetterTtvChannelsEmotes,
getUserAvailableTwitchEmotes,
getTwitchChannelId,
getTwitchUserId,
checkIfTokenIsValid
};

View file

@ -1,96 +0,0 @@
/* global settings, bot, messageId, showChatMessage, messageTemplates, chat, addSingleTooltip,getPostTime */
const { LiveChat } = require('youtube-chat');
const startTime = new Date();
// If channelId is specified, liveId in the current stream is automatically acquired.
// Recommended
// const liveChat = new LiveChat({ handle: settings.YOUTUBE.CHANNEL_HANDLE });
const liveChat = new LiveChat({ liveId: settings.YOUTUBE.LIVE_ID });
// Or specify LiveID in Stream manually.
// const liveChat = new LiveChat({ liveId: '4xDzrJKXOOY' });
// Emit at start of observation chat.
// liveId: string
liveChat.on('start', liveId => {
/* Your code here! */
console.log(liveId);
});
// Emit at end of observation chat.
// reason: string?
liveChat.on('end', reason => {
/* Your code here! */
console.log(reason);
});
// Emit at receive chat.
// chat: ChatItem
liveChat.on('chat', message => {
/* Your code here! */
if (message.timestamp > startTime) {
displayYoutubeMessage(message);
}
});
// Emit when an error occurs
// err: Error or any
liveChat.on('error', err => {
/* Your code here! */
console.log(err);
});
async function startYoutube() {
// Start fetch loop
const ok = await liveChat.start();
if (!ok) {
console.log('Failed to start, check emitted error');
}
}
startYoutube();
async function displayYoutubeMessage(message) {
messageId++;
const article = document.createElement('article');
article.className = 'msg-container sender';
article.setAttribute('id', messageId);
article.innerHTML = messageTemplates.youtubeTemplate;
const userImg = article.querySelector('.user-img');
if (userImg) {
userImg.src = message.author.thumbnail.url;
userImg.setAttribute('tip', '');
}
addSingleTooltip(userImg);
const usernameHtml = article.querySelector('.username');
if (usernameHtml) {
usernameHtml.innerText = message.author.name;
}
const postTime = article.querySelector('.post-time');
if (postTime) {
postTime.innerText = getPostTime();
}
article.appendChild(postTime);
const formattedMessage = article.querySelector('.msg-box');
if (formattedMessage) {
message.message.forEach(entry => {
if (entry.text) {
formattedMessage.innerHTML += entry.text;
} else {
formattedMessage.innerHTML += `<img src="${entry.url}"/>`;
}
});
}
await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => {
formattedMessage.innerHTML = data;
showChatMessage(article);
});
}

View file

@ -1,279 +1,210 @@
/* global pythonPath, a */
const { app, BrowserWindow, ipcMain } = require('electron'); const { app, BrowserWindow, ipcMain } = require('electron');
const { writeIniFile } = require('write-ini-file');
const path = require('path'); const path = require('path');
const http = require('http'); const http = require('http');
const kill = require('kill-process-by-name'); const kill = require('kill-process-by-name');
const ini = require('ini');
const fs = require('fs'); const fs = require('fs');
let resourcesPath = __dirname; let resourcesPath = __dirname;
let settingsPath = null; let settingsPath;
let settings; let settings;
let window; let window;
if (app.isPackaged) { if (app.isPackaged) {
settingsPath = path.join(process.resourcesPath, './settings.json'); settingsPath = path.join(process.resourcesPath, './settings.ini');
pythonPath = path.join(process.resourcesPath, './backend'); pythonPath = path.join(process.resourcesPath, './backend');
resourcesPath = process.resourcesPath; resourcesPath = process.resourcesPath;
} else { } else {
settingsPath = path.join(resourcesPath, './config/settings.json'); settingsPath = path.join(resourcesPath, './config/settings.ini');
pythonPath = path.join(resourcesPath, './backend'); pythonPath = path.join(resourcesPath, './backend');
}
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
} }
async function createWindow() { async function createWindow() {
if (!fs.existsSync(settingsPath)) { if (!fs.existsSync(settingsPath)) {
settings = await createSettingsFile(); console.log(resourcesPath);
} else { await createIniFile();
const file = fs.readFileSync(settingsPath); } else {
settings = JSON.parse(file); settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8'));
}
window = new BrowserWindow({
icon: path.join(__dirname, '/images/icon-512.png'),
width: parseInt(settings.GENERAL.WIDTH),
height: parseInt(settings.GENERAL.HEIGHT),
x: parseInt(settings.GENERAL.POSITION_X),
y: parseInt(settings.GENERAL.POSITION_Y),
frame: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
} }
});
window.loadFile(path.join(__dirname, 'index.html'));
// Create the second window window = new BrowserWindow({
const secondWindow = new BrowserWindow({ icon: path.join(__dirname, '/images/icon-512.png'),
width: 1920, width: parseInt(settings.GENERAL.WIDTH),
height: 1080, height: parseInt(settings.GENERAL.HEIGHT),
// parent: window, // Set the parent window x: parseInt(settings.GENERAL.POSITION_X),
// modal: true, y: parseInt(settings.GENERAL.POSITION_Y),
frame: false, frame: false,
show: true, // Hide the second window initially if needed webPreferences: {
webPreferences: { nodeIntegration: true,
nodeIntegration: true, contextIsolation: false,
contextIsolation: false, enableRemoteModule: true,
enableRemoteModule: true, },
backgroundThrottling: false });
window.loadFile(path.join(__dirname, 'index.html'));
if (!app.isPackaged) {
window.webContents.openDevTools();
} }
});
// Load second.html into the second window window.on('close', (e) => {
secondWindow.loadFile(path.join(__dirname, './modules/facemask/index.html')); settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program
// secondWindow.webContents.setFrameRate(60); const bounds = window.getBounds();
if (!app.isPackaged) { settings.GENERAL.WIDTH = bounds.width;
window.webContents.openDevTools(); settings.GENERAL.HEIGHT = bounds.height;
secondWindow.webContents.openDevTools(); settings.GENERAL.POSITION_X = bounds.x;
} settings.GENERAL.POSITION_Y = bounds.y;
window.on('close', e => { fs.writeFileSync(settingsPath, ini.stringify(settings));
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program });
const bounds = window.getBounds();
settings.GENERAL.WIDTH = bounds.width;
settings.GENERAL.HEIGHT = bounds.height;
settings.GENERAL.POSITION_X = bounds.x;
settings.GENERAL.POSITION_Y = bounds.y;
fs.writeFileSync(settingsPath, JSON.stringify(settings));
});
} }
// app.disableHardwareAcceleration();
// app.disableDomainBlockingFor3DAPIs();
app.whenReady().then(() => { 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') {
// kill('loquendoBot_backend'); app.quit();
app.quit(); }
}
}); });
app.on('activate', () => { app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) { if (BrowserWindow.getAllWindows().length === 0) {
createWindow(); createWindow();
} }
}); });
app.on('before-quit', () => { app.on('before-quit', () => {
window.webContents.send('quit-event'); window.webContents.send('quit-event');
// kill('loquendoBot_backend');
}); });
ipcMain.on('resize-window', (event, width, height) => { ipcMain.on('resize-window', (event, width, height) => {
const browserWindow = BrowserWindow.fromWebContents(event.sender); const browserWindow = BrowserWindow.fromWebContents(event.sender);
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()) {
browserWindow.maximize(); browserWindow.maximize();
} else { } else {
browserWindow.unmaximize(); browserWindow.unmaximize();
}
});
ipcMain.on('close-window', event => {
const browserWindow = BrowserWindow.fromWebContents(event.sender);
kill('loquendoBot_backend');
browserWindow.close();
app.quit();
});
ipcMain.on('restart', event => {
app.relaunch();
});
ipcMain.on('environment', event => {
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
});
async function createSettingsFile() {
const settingsx = {
GENERAL: {
VOICE_ENABLED: true,
NOTIFICATION_ENABLED: true,
POSITION_X: 0,
POSITION_Y: 0,
WIDTH: 1024,
HEIGHT: 768,
LANGUAGE: 'none',
LANGUAGE_INDEX: '0',
PORT: 9000,
VIEWERS_PANEL: false,
LOCATION: pythonPath,
ZOOMLEVEL: 1
},
LANGUAGE: {
USE_DETECTION: false,
TRANSLATE_TO: 'none',
LANGUAGE_INDEX: '0',
BROADCAST_TRANSLATION: false,
OUTPUT_TO_TTS: false,
TRANSLATE_TO_INDEX: 0,
SEND_TRANSLATION: false,
SEND_TRANSLATION_IN: 'none',
SEND_TRANSLATION_OUT: 'none'
},
TTS: {
USE_TTS: false,
PRIMARY_VOICE: '',
PRIMARY_TTS_LANGUAGE: 'none',
SECONDARY_VOICE: '',
SECONDARY_TTS_LANGUAGE: 'none',
PRIMARY_TTS_LANGUAGE_INDEX: 0,
SECONDARY_TTS_LANGUAGE_INDEX: 0
},
STT: {
USE_STT: false,
MICROPHONE_ID: 'default',
SELECTED_MICROPHONE: 'default',
MICROPHONE: 0,
LANGUAGE: ''
},
AUDIO: {
USE_NOTIFICATION_SOUNDS: false,
SELECTED_NOTIFICATION_AUDIO_DEVICE: 'default',
NOTIFICATION_AUDIO_DEVICE: 0,
NOTIFICATION_SOUND: 0,
NOTIFICATION_VOLUME: 50,
SELECTED_TTS_AUDIO_DEVICE: 0,
TTS_AUDIO_DEVICE: 'default',
TTS_VOLUME: 50
},
THEME: {
USE_CUSTOM_THEME: false,
MAIN_COLOR_1: '#cdc1c1',
MAIN_COLOR_2: '#b12020',
MAIN_COLOR_3: '#6c4104',
MAIN_COLOR_4: '#532d2d',
TOP_BAR: '#c8ff00',
MID_SECTION: '#6b8578',
CHAT_BUBBLE_BG: '#447466',
CHAT_BUBBLE_HEADER: '#ffffff',
CHAT_BUBBLE_MESSAGE: '#b5b5b5'
},
TWITCH: {
USE_TWITCH: false,
SEND_CHAT: false,
CHANNEL_NAME: '',
CHANNEL_USER_ID: '',
USERNAME: '',
USER_ID: '',
USER_LOGO_URL: '',
OAUTH_TOKEN: '',
BETTERTTV_CHANNELS: [
{ value: 'turtlemaw', text: 'turtlemaw' },
{ value: 'tO_Ot', text: 'tO_Ot' },
{ value: 'adew', text: 'adew' }
],
CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9'
},
TROVO: {
USE_TROVO: false,
SEND_CHAT: false,
CHANNEL_NAME: '',
CHANNEL_ID: '',
USERNAME: '',
USER_LOGO_URL: '',
USER_ID: '',
CLIENT_ID: '8d32385a4be4a29e345aedaf23ca772f',
OAUTH_TOKEN: ''
},
YOUTUBE: {
USE_YOUTUBE: false,
SEND_CHAT: false,
CHANNEL_ID: '',
CHANNEL_HANDLE: '',
LIVE_ID: ''
},
DLIVE: {
USE_DLIVE: false,
SEND_CHAT: false,
API_KEY: ''
},
MODULES: {
USE_MODULES: false,
USE_VTUBER: false,
USE_PNGTUBER: false,
USE_CHATBUBBLE: false
},
AMAZON: {
USE_AMAZON: false,
ACCESS_KEY: '',
ACCESS_SECRET: '',
PRIMARY_VOICE: '',
SECONDARY_VOICE: '',
CHARACTERS_USED: 0
},
GOOGLE: {
USE_GOOGLE: false,
API_KEY: '',
PRIMARY_VOICE: '',
SECONDARY_VOICE: '',
CHARACTERS_USED: 0
} }
}; });
fs.writeFile(settingsPath, JSON.stringify(settingsx), error => { ipcMain.on('close-window', (event) => {
if (error) { const browserWindow = BrowserWindow.fromWebContents(event.sender);
console.error(error); kill('loquendoBot_backend');
} browserWindow.close();
}); app.quit();
return settingsx; });
ipcMain.on('restart', (event) => {
app.relaunch();
});
ipcMain.on('environment', (event) => {
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
});
async function createIniFile() {
await writeIniFile(settingsPath, {
GENERAL: {
VOICE_ENABLED: true,
NOTIFICATION_ENABLED: true,
POSITION_X: 0,
POSITION_Y: 0,
WIDTH: 1024,
HEIGHT: 768,
LANGUAGE: 'EN',
PORT: 9000,
VIEWERS_PANEL: false,
LOCATION: pythonPath,
},
LANGUAGE: {
USE_DETECTION: false,
},
TTS: {
USE_TTS: true,
PRIMARY_VOICE: '',
PRIMARY_TTS_LANGUAGE: 'EN',
SECONDARY_VOICE: '',
SECONDARY_TTS_LANGUAGE: 'EN',
},
STT: {
USE_STT: false,
MICROPHONE_ID: 'default',
SELECTED_MICROPHONE: 'default',
MICROPHONE: 0,
LANGUAGE: 'vosk-model-small-es-0.42',
},
AUDIO: {
USE_NOTIFICATION_SOUNDS: true,
NOTIFICATION_AUDIO_DEVICE: 0,
NOTIFICATION_SOUND: 0,
NOTIFICATION_VOLUME: 50,
SELECTED_TTS_AUDIO_DEVICE: 0,
TTS_AUDIO_DEVICE: 'default',
TTS_VOLUME: 50,
},
THEME: {
USE_CUSTOM_THEME: false,
MAIN_COLOR_1: '#cdc1c1',
MAIN_COLOR_2: '#b12020',
MAIN_COLOR_3: '#6c4104',
MAIN_COLOR_4: '#532d2d',
TOP_BAR: '#c8ff00',
MID_SECTION: '#6b8578',
CHAT_BUBBLE_BG: '#447466',
CHAT_BUBBLE_HEADER: '#ffffff',
CHAT_BUBBLE_MESSAGE: '#b5b5b5',
},
TWITCH: {
USE_TWITCH: false,
CHANNEL_NAME: '',
USERNAME: '',
USER_ID: '',
USER_LOGO_URL: '',
OAUTH_TOKEN: '',
CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9',
},
MODULES: {
USE_MODULES: false,
USE_VTUBER: false,
USE_CHATBUBBLE: false,
},
AMAZON: {
USE_AMAZON: false,
ACCESS_KEY: '',
ACCESS_SECRET: '',
PRIMARY_VOICE: '',
SECONDARY_VOICE: '',
CHARACTERS_USED: 0,
},
GOOGLE: {
USE_GOOGLE: false,
API_KEY: '',
PRIMARY_VOICE: '',
SECONDARY_VOICE: '',
CHARACTERS_USED: 0,
},
}).then(() => {
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8'));
});
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,20 +1,23 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<title>Chat</title> <title>Chat</title>
<script <script
src="https://cdn.socket.io/4.6.0/socket.io.min.js" src="https://cdn.socket.io/4.6.0/socket.io.min.js"
integrity="sha384-c79GN5VsunZvi+Q/WObgk2in0CbZsHnjEqvFxC5DxHn9lTfNce2WW6h2pH6u/kF+" integrity="sha384-c79GN5VsunZvi+Q/WObgk2in0CbZsHnjEqvFxC5DxHn9lTfNce2WW6h2pH6u/kF+"
crossorigin="anonymous" crossorigin="anonymous"
></script> ></script>
<link rel="stylesheet" href="./fonts/FRAMCDN/font.css" /> <link rel="stylesheet" href="./fonts/FRAMCDN/font.css" />
<link href="main.css" rel="stylesheet" /> <link href="main.css" rel="stylesheet" />
</head> </head>
<body>
<body> <!-- #region Main chat box-->
<div id="chatBox" class="message-window"></div> <div class="OptionPanel show" id="Chat">
<emoji-picker class="dark"></emoji-picker> <div id="chatBox" class="message-window">
</body> <div class="texts"></div>
<script src="main.js"></script> </div>
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script> </div>
<script src="main.js"></script>
<video id="camera" autoplay></video>
</body>
</html> </html>

View file

@ -1,598 +1,171 @@
@font-face { body {
font-family: 'FRAMDCN'; background-color: transparent;
src: url(../fonts/FRAMCDN/FRAMDCN.woff); font-family: 'FRAMDCN';
} }
:root { :root {
overflow: hidden; --variable: 2s;
--main-color1: #6e2c8c; --buttonBackground: #bf2c2c;
--main-color1-temp: #6e2c8c;
/*Left bar and top right bar*/
--main-color2: white;
--main-color2-temp: white;
/*Icons and text*/
--main-color3: #211e1e;
--main-color3-temp: #211e1e;
/*Buttons and input*/
--main-color4: #2f2c34;
--main-color4-temp: #2f2c34;
--top-bar: #100b12;
--top-bar-temp: #100b12;
--mid-section: #352d3d;
--mid-section-temp: #352d3d;
--chat-bubble: #7a6d7f;
--chat-bubble-header: #141414;
--chat-bubble-username: white;
--chat-bubble-message: white;
--chat-bubble-temp: #7a6d7f;
--chat-bubble-header-temp: #141414;
--chat-bubble-message-temp: white;
} }
h1 { .thomas {
font-family: 'FRAMDCN'; position: relative;
float: center;
display: inline-block;
} }
.message-window { .speechbubble {
height: calc(100% - 60px); display: block;
overflow: hidden;
overflow-y: auto;
display: flex;
align-items: center;
flex-direction: column-reverse;
font-family: 'FRAMDCN';
position: relative;
z-index: 1;
}
.input-box {
display: flex;
border: none;
width: 100%;
height: 30px;
font-size: 16px;
}
.userText {
color: var(--chat-bubble-message);
font-family: Helvetica;
font-size: 16px;
text-align: right;
clear: both;
}
.userText span {
line-height: 1.5em;
display: inline-block;
background: #5ca6fa;
padding: 10px;
border-radius: 8px;
border-bottom-right-radius: 2px;
max-width: 80%;
margin-right: 10px;
animation: floatup 0.5s forwards;
}
.botText {
color: #000;
font-family: Helvetica;
font-weight: normal;
font-size: 16px;
text-align: left;
}
.botText span {
line-height: 1.5em;
display: inline-block;
background: #e0e0e0;
padding: 10px;
border-radius: 8px;
border-bottom-left-radius: 2px;
max-width: 80%;
margin-left: 10px;
animation: floatup 0.5s forwards;
}
@keyframes floatup {
from {
transform: translateY(14px);
opacity: 0;
}
to {
transform: translateY(0px);
opacity: 1;
}
}
@media screen and (max-width: 600px) {
.full-chat-block {
width: 100%;
border-radius: 0px;
}
.chat-bar-collapsible {
position: fixed;
bottom: 0; bottom: 0;
right: 0; position: absolute;
width: 100%; z-index: -1;
}
.collapsible {
width: 100%;
border: 0px;
border-radius: 0px;
}
} }
::-webkit-scrollbar { .fade-outx {
width: 4px; animation: fade-outx var(--variable) linear;
} }
::-webkit-scrollbar-thumb { @keyframes fade-outx {
background-color: #4c4c6a; from {
border-radius: 2px; opacity: 1;
}
to {
opacity: 0;
}
} }
.chatBox { .fade-outxx {
width: 300px; animation: fade-outxx var(--variable) linear;
height: 400px;
max-height: 400px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 0 4px var(--main-color4);
} }
.chat-window { @keyframes fade-outxx {
flex: auto; from {
max-height: calc(100% - 60px); opacity: 1;
background: #2f323b; }
overflow: auto;
to {
opacity: 0;
}
} }
.chat-input { .bounce-in {
height: 30px; animation: bounce-in 1s ease;
display: flex;
flex: 0 0 auto;
height: 60px;
background: var(--main-color3);
} }
.chat-input input { @keyframes bounce-in {
height: 59px; 0% {
line-height: 60px; opacity: 0;
outline: 0 none; transform: scale(0.3);
border: none; }
width: calc(100% - 60px);
color: var(--chat-bubble-message); 50% {
text-indent: 10px; opacity: 1;
font-size: 12pt; transform: scale(1.05);
padding: 0; }
background: var(--main-color3);
70% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
} }
.chat-input button { .bounce-inx {
float: right; animation: bounce-inx 1s ease;
outline: 0 none;
border: none;
background: var(--main-color3);
height: 40px;
width: 40px;
border-radius: 50%;
padding: 2px 0 0 0;
margin: 10px;
} }
.chat-input input[good] + button { @keyframes bounce-inx {
box-shadow: 0% {
0 0 2px rgba(0, 0, 0, 0.12), opacity: 0;
0 2px 4px rgba(0, 0, 0, 0.24); }
}
.chat-input input[good] + button:hover { 50% {
box-shadow: opacity: 1;
0 8px 17px 0 rgba(0, 0, 0, 0.2), }
0 6px 20px 0 rgba(0, 0, 0, 0.19);
/* filter: brightness(150%); */
}
.chat-input input[good] + button path {
fill: var(--chat-bubble-message);
} }
.msg-container { .msg-container {
direction: ltr; position: static;
position: static; display: inline-block;
width: 100%; width: 100%;
padding: 10px 0px 0px 0px; padding-top: 10px;
display: grid;
grid-template: 1fr / 1fr;
align-self: start;
} }
.msg-container > * { .message-window {
grid-column: 1 / 1; height: calc(100% - 50px);
grid-row: 1 / 1; overflow: hidden;
overflow-y: hidden;
display: flex;
flex-direction: column;
width: 80%;
margin: auto;
background: transparent;
} }
.msg-container.sender { .message-window::before {
place-items: self-start; content: '';
flex: 1 0 0px;
} }
.msg-container.user { .OptionPanel {
place-items: self-end; flex: 3;
display: none;
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: calc(100% - 25px);
background: transparent;
} }
.msg-box { .OptionPanel.show {
background: var(--chat-bubble); display: block;
color: white;
min-width: 100px;
border-radius: 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);
width: fit-content;
position: relative;
align-self: start;
} }
.msg-box.sender { .message {
margin: 25px 25px 0px 35px; text-align: left;
max-width: 100%;
height: auto;
min-width: 125px;
hyphens: auto;
bottom: 0;
right: 0;
float: right;
overflow-wrap: break-word;
} }
.msg-box.user { .message {
text-align: left; position: relative;
margin: 25px 35px 0px 0px; border: 2px solid #ff80e1;
box-shadow: 0 2px 10px rgba(255, 128, 225, 0.5);
background: linear-gradient(45deg, rgb(15, 12, 41, 0.7), rgb(48, 43, 99, 0.7));
/* background: linear-gradient(45deg, rgba(72, 0, 154, 0.7), rgba(138, 43, 226, 0.7)); */
color: white;
padding: 15px;
border-radius: 20px;
} }
.msg-box-user-temp { .message::after {
background: var(--chat-bubble-temp);
} }
.user-img { .arrow {
display: inline-block; content: '';
position: relative; border: 2px solid #ff80e1;
border-radius: 50%; position: absolute;
height: 50px; left: 50%;
width: 50px; top: 100%;
z-index: 5; transform: translateX(-50%) rotate(180deg);
align-self: start; border-width: 10px;
border-style: solid;
border-color: transparent transparent rgb(255, 128, 225, 0.7) transparent;
color: #ff80e1;
} }
.messages.user { .sender {
margin-right: 20px; color: #ff80e1;
} font-size: 14pt;
.msg {
font-size: 12pt;
color: var(--chat-bubble-message);
margin: 0 0 0 0;
}
.msg-temp {
color: var(--chat-bubble-message-temp);
}
.timestamp {
color: var(--chat-bubble-header);
font-size: 10pt;
align-items: center;
font-family: 'xxii_avenmedium';
}
.timestamp-temp {
color: var(--chat-bubble-header-temp);
}
.username {
background-color: var(--main-color4);
color: white;
position: relative;
border-radius: 5px;
z-index: 3;
align-self: start;
}
.username.sender {
padding: 0px 5px 5px 30px;
margin: 20px 5px 5px 25px;
}
.username.user {
padding: 0px 30px 5px 5px;
margin: 20px 30px 5px 5px;
}
.username-temp {
color: var(--chat-bubble-header-temp);
}
.post-time {
font-size: 8pt;
color: white;
display: inline-block;
background-color: var(--main-color4);
position: relative;
z-index: 2;
border-radius: 5px;
align-self: start;
}
.post-time.sender {
padding: 5px 5px 5px 15px;
margin: 0px 0px 0px 50px;
}
.post-time.user {
padding: 5px 15px 5px 5px;
margin: 0px 50px 0px 0px;
}
.mmg {
display: flex;
}
.img {
height: 100%;
width: 100%;
border-radius: 50%;
}
.status-circle {
width: 20px;
border-radius: 50%;
z-index: 6;
position: relative;
align-self: start;
}
.status-circle.sender {
margin-left: 40px;
}
.status-circle.user {
margin-right: 40px;
}
.menu-select {
font-size: 0.9rem;
height: 40px;
border-radius: 20px;
background-color: var(--main-color3);
color: var(--main-color2);
align-items: center;
border: 0px;
padding-left: 10px;
width: 300px;
font-size: 100%;
padding: 10px;
padding-right: 25px;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='34' viewBox='0 0 24 24' width='32' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
background-repeat: no-repeat;
background-position-x: 100%;
background-position-y: 5px;
}
.top-select {
width: auto;
height: 24px;
padding: 0px;
margin: 0px;
background-color: transparent;
color: white;
-webkit-appearance: none;
-moz-appearance: none;
border: none;
}
.info-image {
width: 50px;
height: 50px;
}
.top-select option {
margin: 40px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
background-color: var(--top-bar);
}
.AdvancedMenu {
border: 1px var(--main-color2) solid;
margin-top: 10px;
min-width: 555px;
border-radius: 5px;
border-radius: 5px;
}
.legendStyle {
margin-left: 1em;
padding: 0.2em 0.8em;
display: flex;
align-items: center;
}
.AdvancedMenuRow {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
justify-content: left;
margin-bottom: 10px;
}
.AdvancedMenuLabel {
font-size: 10pt;
padding-right: 5px;
margin-left: 10px;
width: 125px;
}
.AdvancedMenuLabel2 {
font-size: 10pt;
padding-right: 5px;
margin-left: 10px;
}
.AdvancedMenuLabel3 {
font-size: 12pt;
padding-right: 5px;
margin-left: 10px;
}
#SaveAdvancedSettingsButton {
margin-left: 10px;
}
.toggle {
position: relative;
display: inline-block;
width: 60px;
height: 40px;
background-color: var(--main-color3);
border-radius: 20px;
}
/* After slide changes */
.toggle:after {
content: '';
position: absolute;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: var(--main-color2);
left: 5px;
top: 5px;
}
/* Checkbox checked effect */
.checkbox:checked + .toggle::after {
left: 25px;
}
/* Checkbox checked toggle label bg color */
.checkbox:checked + .toggle {
background-color: var(--main-color1);
}
/* Checkbox vanished */
.checkbox {
display: none;
}
/* Small toggle */
/* toggle in label designing */
.toggle-small {
position: relative;
display: inline-block;
width: 30px;
height: 20px;
background-color: var(--main-color3);
border-radius: 10px;
margin-left: 10px;
}
/* After slide changes */
.toggle-small:after {
content: '';
position: absolute;
width: 15px;
height: 15px;
border-radius: 50%;
background-color: white;
left: 2px;
top: 2px;
}
/* Checkbox checked effect */
.checkbox:checked + .toggle-small::after {
left: 13px;
}
/* Checkbox checked toggle label bg color */
.checkbox:checked + .toggle-small {
background-color: var(--main-color1);
}
.emotes {
position: relative;
cursor: pointer;
}
.dark {
display: none;
position: absolute;
z-index: 1;
top: -400px;
}
.emotes:hover .dark {
display: block;
}
.fi {
position: relative;
z-index: 5;
border-radius: 50%;
}
.translation-header {
background-color: var(--main-color4);
border-radius: 5px;
width: fit-content;
padding: 5px;
margin: 10px 0px 5px -5px;
position: relative;
}
.translation-message {
position: relative;
margin: 20px 0px 0px 0px;
}
.translation-message.user {
margin: -20px 0px 0px 0px;
}
.translation-icon {
position: relative;
padding: 0px 0px 0px 0px;
margin: -45px 0px 0px -40px;
top: 30px;
}
.language-icon {
position: relative;
top: 45px;
}
.flag-icon {
width: 20px !important;
height: 20px !important;
left: 18px;
}
.flag-icon.user {
left: -18px;
top: -15px;
}
.user-flag {
left: unset;
right: 18px;
top: -65px;
} }

View file

@ -1,89 +1,123 @@
/* global io */
// Connect to the Socket.IO server // Connect to the Socket.IO server
const socket = io(); const socket = io();
const twitchTemplate = `
<img class="user-img" src="" />
<img class="status-circle sender" src="./images/twitch-icon.png" tip="twitch" />
<span class="post-time sender"></span>
<span class="username sender"></span>
<div class="msg-box sender"></div>
`.trim();
const emojiPicker = document.body.querySelector('emoji-picker');
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);
}
});
});
// Emit a message to the server // Emit a message to the server
socket.emit('message', 'Hello, Server!'); socket.emit('message', 'Hello, Server!');
async function displayChatMessage(message) { function getPostTime() {
const article = document.createElement('article'); const d = new Date();
article.className = 'msg-container sender'; document.body.querySelectorAll('.container').innerHTML = d.getHours();
article.setAttribute('id', message.messageId); const hours = d.getHours();
const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
article.innerHTML = twitchTemplate; const time = `${hours}:${minutes}`;
const userImg = article.querySelector('.user-img'); return time;
if (userImg) {
userImg.src = message.image;
}
const usernameHtml = article.querySelector('.username');
if (usernameHtml) {
usernameHtml.innerText = message.username;
}
const postTime = article.querySelector('.post-time');
if (postTime) {
postTime.innerText = message.postTime;
}
article.appendChild(postTime);
const formattedMessage = article.querySelector('.msg-box');
formattedMessage.innerHTML = message.message;
// if (formattedMessage) {
// messageObject.forEach(entry => {
// if (entry.text) {
// formattedMessage.innerHTML += entry.text;
// } else {
// formattedMessage.innerHTML += entry.html;
// }
// });
// }
await replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => {
formattedMessage.innerHTML = data;
showChatMessage(article);
});
} }
function showChatMessage(article) { function showChatMessage(article) {
document.getElementById('chatBox').appendChild(article); const main = document.querySelector('#chatBox');
main.appendChild(article);
main.scrollTop = main.scrollHeight;
}
const messages = document.body.querySelectorAll('.msg-container'); let textStreamContainer;
let x;
// const totalDuration = 5000; // Total duration in milliseconds
// const charactersPerSecond = 20; // Adjust the number of characters to display per second
const lastMessage = messages[messages.length - 1]; // const streamingSpeed = totalDuration / (textToStream.length / charactersPerSecond);
lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' });
let currentIndex = 0;
let messageStream = '';
let tempMessageObject = '';
let fullMessageLength = 0;
function getFullMessageLength(text) {
let fullMessageLength = 0;
text.forEach((element) => {
if (element.text) {
fullMessageLength += element.text.length;
}
element.html;
fullMessageLength += 1;
});
return fullMessageLength;
}
function streamText() {
// if (currentIndex < fullMessageLength) {
// textStreamContainer.innerHTML += messageStream.filtered.charAt(currentIndex);
// currentIndex++;
// setTimeout(streamText, 50);
// }
if (currentIndex < messageStream.length) {
textStreamContainer.innerHTML += messageStream.charAt(currentIndex);
currentIndex++;
setTimeout(streamText, 50);
} else {
currentIndex = 0;
x.classList.add('fade-outx');
}
}
function displayTwitchMessage(logoUrl, username, messageObject) {
if (!messageObject) {
return;
}
const root = document.querySelector(':root');
root.style.setProperty('--variable', '5s');
const article = document.createElement('article');
x = article;
article.className = 'msg-container';
const placeMessage = `
<div class="thomas bounce-in">
<div class="message"></div>
<div class="sender"></div>
<div class="speechbubble"></div>
<div class="arrow"></div>
</div>
`.trim();
article.innerHTML = placeMessage;
const msg = article.querySelector('.message');
msg.innerHTML = `<div class="sender">${username}</div>`; //\n${message}`;
msg.style.fontSize = '12pt';
showChatMessage(article);
const elements = document.getElementsByClassName('msg-container');
if (elements.length > 1) {
elements[0].remove();
}
article.addEventListener('animationend', (e) => {
if (e.animationName == 'fade-outx') {
article.remove();
}
});
if (elements.length > 1) {
elements[0].classList.add('fade-outxx');
elements[0].addEventListener('animationend', (e) => {
if (e.animationName == 'fade-outxx') {
elements[0].remove();
}
});
}
// fullMessageLength = getFullMessageLength(messageObject);
messageStream = messageObject.filtered;
textStreamContainer = document.querySelector('.message');
streamText();
} }
// // Receive a message from the server // // Receive a message from the server
socket.on('chat-in', message => { socket.on('message', (logoUrl, username, message, messageDuration) => {
displayChatMessage(message); displayTwitchMessage(logoUrl, username, message);
}); });

View file

@ -1,14 +0,0 @@
{
"name": "chat",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,10 +0,0 @@
@font-face {
font-family: 'FRAMDCN';
src: local('FRAMDCN'), url('./FRAMDCN.woff') format('woff');
}
/* use this class to attach this font to any element i.e. <p class="fontsforweb_fontid_1381">Text with this font applied</p> */
.fontsforweb_fontid_1381 {
font-family: 'FRAMDCN' !important;
}
/* Font downloaded from FontsForWeb.com */

View file

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title> web font from FontsForWeb.com</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="font.css">
<style type="text/css">
/* when @font-face is defined(it is in font.css) you can add the font to any rule by using font-family */
h1 {
font-family: 'FRAMDCN';
}
</style>
</head>
<body>
<h1>Thank you for using FontsForWeb.com</h1>
<p class="fontsforweb_fontid_1381">Look in the source of this file to see how to embed this font on your website</p>
</body>
</html>

View file

@ -1,2 +0,0 @@
Original download page
http://ttfonts.net/font/606_FranklinGothicMediumCond.htm

View file

@ -1,23 +0,0 @@
<!doctype html>
<html>
<head>
<title>Chat</title>
<script
src="https://cdn.socket.io/4.6.0/socket.io.min.js"
integrity="sha384-c79GN5VsunZvi+Q/WObgk2in0CbZsHnjEqvFxC5DxHn9lTfNce2WW6h2pH6u/kF+"
crossorigin="anonymous"
></script>
<link rel="stylesheet" href="./fonts/FRAMCDN/font.css" />
<link href="main.css" rel="stylesheet" />
</head>
<body>
<!-- #region Main chat box-->
<div class="OptionPanel show" id="Chat">
<div id="chatBox" class="message-window">
<div class="texts"></div>
</div>
</div>
<script src="main.js"></script>
<video id="camera" autoplay></video>
</body>
</html>

View file

@ -1,174 +0,0 @@
body {
background-color: transparent;
font-family: 'FRAMDCN';
}
:root {
--variable: 2s;
--buttonBackground: #bf2c2c;
}
.thomas {
position: relative;
float: center;
/* display: inline-block; */
}
.speechbubble {
display: block;
bottom: 0;
position: absolute;
z-index: -1;
}
.fade-outx {
animation: fade-outx var(--variable) linear;
}
@keyframes fade-outx {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.fade-outxx {
animation: fade-outxx var(--variable) linear;
}
@keyframes fade-outxx {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.bounce-in {
animation: bounce-in 1s ease;
}
@keyframes bounce-in {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}
.bounce-inx {
animation: bounce-inx 1s ease;
}
@keyframes bounce-inx {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
}
.msg-container {
direction: ltr;
position: static;
width: 100%;
padding: 10px 0px 0px 0px;
display: grid;
grid-template: 1fr / 1fr;
align-self: center;
width: fit-content;
}
.msg-container > * {
grid-column: 1 / 1;
grid-row: 1 / 1;
}
.message-window {
height: calc(100% - 50px);
overflow: hidden;
overflow-y: hidden;
display: flex;
flex-direction: column;
width: 80%;
margin: auto;
background: transparent;
}
.message-window::before {
content: '';
flex: 1 0 0px;
}
.OptionPanel {
flex: 3;
display: none;
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: calc(100% - 25px);
background: transparent;
}
.OptionPanel.show {
display: block;
}
.message {
text-align: left;
max-width: 100%;
height: auto;
min-width: fit-content;
hyphens: auto;
/* bottom: 0; */
/* right: 0; */
/* float: right; */
overflow-wrap: break-word;
position: relative;
border: 2px solid #ff80e1;
/* box-shadow: 0 2px 10px rgba(255, 128, 225, 0.5); */
background: linear-gradient(45deg, rgb(15, 12, 41, 0.7), rgb(48, 43, 99, 0.7));
/* background: linear-gradient(45deg, rgba(72, 0, 154, 0.7), rgba(138, 43, 226, 0.7)); */
color: white;
padding: 15px;
border-radius: 20px;
margin-bottom: 10px;
}
.arrow {
content: '';
border: 2px solid #ff80e1;
position: absolute;
left: 50%;
transform: translateX(-50%) rotate(180deg);
border-width: 10px;
border-style: solid;
border-color: transparent transparent rgb(255, 128, 225, 0.7) transparent;
color: #ff80e1;
bottom: -10px;
}
.sender {
color: #ff80e1;
font-size: 14pt;
}

View file

@ -1,125 +0,0 @@
/* global io */
// Connect to the Socket.IO server
const socket = io();
// Emit a message to the server
socket.emit('message', 'Hello, Server!');
function getPostTime() {
const d = new Date();
document.body.querySelectorAll('.container').innerHTML = d.getHours();
const hours = d.getHours();
const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
const time = `${hours}:${minutes}`;
return time;
}
function showChatMessage(article) {
const main = document.querySelector('#chatBox');
main.appendChild(article);
main.scrollTop = main.scrollHeight;
}
let textStreamContainer;
let x;
// const totalDuration = 5000; // Total duration in milliseconds
// const charactersPerSecond = 20; // Adjust the number of characters to display per second
// const streamingSpeed = totalDuration / (textToStream.length / charactersPerSecond);
let currentIndex = 0;
let messageStream = '';
const tempMessageObject = '';
const fullMessageLength = 0;
function getFullMessageLength(text) {
let fullMessageLength = 0;
text.forEach(element => {
if (element.text) {
fullMessageLength += element.text.length;
}
// element.html;
fullMessageLength += 1;
});
return fullMessageLength;
}
function streamText() {
// if (currentIndex < fullMessageLength) {
// textStreamContainer.innerHTML += messageStream.filtered.charAt(currentIndex);
// currentIndex++;
// setTimeout(streamText, 50);
// }
if (currentIndex < messageStream.length) {
textStreamContainer.innerHTML += messageStream.charAt(currentIndex);
currentIndex++;
setTimeout(streamText, 50);
} else {
currentIndex = 0;
x.classList.add('fade-outx');
}
}
function displayTwitchMessage(message) {
if (!message.filteredMessage) {
return;
}
const root = document.querySelector(':root');
root.style.setProperty('--variable', '5s');
const article = document.createElement('article');
x = article;
article.className = 'msg-container';
const placeMessage = `
<div class="thomas bounce-in">
<div class="message"></div>
<div class="sender"></div>
<div class="speechbubble"></div>
<div class="arrow"></div>
</div>
`.trim();
article.innerHTML = placeMessage;
const msg = article.querySelector('.message');
msg.innerHTML = `<div class="sender">${message.username}</div>`; // \n${message}`;
msg.style.fontSize = '12pt';
showChatMessage(article);
const elements = document.getElementsByClassName('msg-container');
if (elements.length > 1) {
elements[0].remove();
}
article.addEventListener('animationend', e => {
if (e.animationName === 'fade-outx') {
article.remove();
}
});
if (elements.length > 1) {
elements[0].classList.add('fade-outxx');
elements[0].addEventListener('animationend', e => {
if (e.animationName === 'fade-outxx') {
elements[0].remove();
}
});
}
// fullMessageLength = getFullMessageLength(messageObject);
messageStream = message.filteredMessage;
textStreamContainer = document.querySelector('.message');
streamText();
}
// // Receive a message from the server
socket.on('message', message => {
displayTwitchMessage(message);
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,478 +0,0 @@
const { FaceLandmarker, HandLandmarker, PoseLandmarker, FilesetResolver, DrawingUtils } = require('@mediapipe/tasks-vision');
const videoBlendShapes = document.getElementById('video-blend-shapes');
const videoSelect = document.querySelector('select#videoSource');
const videoElement = document.getElementById('video');
const canvasElement = document.getElementsByClassName('output_canvas')[0];
const canvasCtx = canvasElement.getContext('2d');
let cameraRunning = false;
let cameraVisible = true;
let cameraFPS = 30;
let cameraWidth = 640;
let cameraHeight = 480;
let lastVideoTime = -1;
const drawingUtils = new DrawingUtils(canvasCtx);
getDevices().then(gotDevices);
let detections = [];
document.body.querySelector('#cameraVisible').addEventListener('click', async () => {
cameraVisible = !cameraVisible;
if (cameraVisible) {
document.body.querySelector('#cameraVisible').innerHTML = 'Hide Camera';
document.body.querySelector('#video').style.display = '';
} else {
document.body.querySelector('#cameraVisible').innerHTML = 'Show Camera';
document.body.querySelector('#video').style.display = 'none';
}
});
document.body.querySelector('#cameraRunning').addEventListener('click', async () => {
cameraRunning = !cameraRunning;
if (cameraRunning) {
getStream();
document.body.querySelector('#cameraRunning').innerHTML = 'Stop Camera';
} else {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
document.body.querySelector('#cameraRunning').innerHTML = 'Start Camera';
}
});
document.body.querySelector('#cameraResolution').addEventListener('change', async e => {
cameraWidth = e.target.options[e.target.selectedIndex].getAttribute('width');
cameraHeight = e.target.options[e.target.selectedIndex].getAttribute('height');
});
document.body.querySelector('#cameraFPS').addEventListener('change', async () => {
cameraFPS = document.body.querySelector('#cameraFPS').value;
});
// -------------- Face Detection --------------
let faceDetectionDelegate = 'GPU';
let minFaceDetectionConfidence = 0.5;
document.getElementById('minFaceDetectionConfidenceValue').innerHTML = minFaceDetectionConfidence;
let minFacePresenceConfidence = 0.5;
document.getElementById('minFacePresenceConfidenceValue').innerHTML = minFacePresenceConfidence;
let minFaceTrackingConfidence = 0.5;
document.getElementById('minFaceTrackingConfidenceValue').innerHTML = minFaceTrackingConfidence;
let faceTrackingEnabled = false;
let hideFace = false;
let faceLandmarker;
let resultsFace;
document.body.querySelector('#face').addEventListener('click', async () => {
faceTrackingEnabled = !faceTrackingEnabled;
if (faceTrackingEnabled) {
createFaceLandmarker();
document.body.querySelector('#face').innerHTML = 'Disable face detection';
} else {
faceLandmarker = null;
document.body.querySelector('#face').innerHTML = 'Enable face detection';
}
});
document.body.querySelector('#hideFace').addEventListener('click', async () => {
hideFace = !hideFace;
if (hideFace) {
document.body.querySelector('#hideFace').innerHTML = 'Show face detection';
} else {
document.body.querySelector('#hideFace').innerHTML = 'Hide face detection';
}
});
document.body.querySelector('#faceDetectionDelegate').addEventListener('change', async () => {
faceDetectionDelegate = document.getElementById('faceDetectionDelegate').value;
if (faceTrackingEnabled) {
createFaceLandmarker();
}
});
document.body.querySelector('#minFaceDetectionConfidence').addEventListener('change', async () => {
minFaceDetectionConfidence = parseInt(document.getElementById('minFaceDetectionConfidence').value);
document.getElementById('minFaceDetectionConfidenceValue').innerHTML = minFaceDetectionConfidence;
if (faceTrackingEnabled) {
createFaceLandmarker();
}
});
document.body.querySelector('#minFacePresenceConfidence').addEventListener('change', async () => {
minFacePresenceConfidence = parseInt(document.getElementById('minFacePresenceConfidence').value);
document.getElementById('minFacePresenceConfidenceValue').innerHTML = minFacePresenceConfidence;
if (faceTrackingEnabled) {
createFaceLandmarker();
}
});
document.body.querySelector('#minFaceTrackingConfidence').addEventListener('change', async () => {
minFaceTrackingConfidence = parseInt(document.getElementById('minFaceTrackingConfidence').value);
document.getElementById('minFaceTrackingConfidenceValue').innerHTML = minFaceTrackingConfidence;
if (faceTrackingEnabled) {
createFaceLandmarker();
}
});
// -------------- Hand Detection --------------
let handDetectionDelegate = 'GPU';
let minHandDetectionConfidence = 0.5;
document.getElementById('minHandDetectionConfidenceValue').innerHTML = minHandDetectionConfidence;
let minHandPresenceConfidence = 0.5;
document.getElementById('minHandPresenceConfidenceValue').innerHTML = minHandPresenceConfidence;
let minHandTrackingConfidence = 0.5;
document.getElementById('minHandTrackingConfidenceValue').innerHTML = minHandTrackingConfidence;
let handTrackingEnabled = false;
let hideHand = false;
let handLandmarker;
let resultsHands;
document.body.querySelector('#hand').addEventListener('click', async () => {
handTrackingEnabled = !handTrackingEnabled;
if (handTrackingEnabled) {
createHandLandmarker();
document.body.querySelector('#hand').innerHTML = 'Disable hand detection';
} else {
handLandmarker = null;
document.body.querySelector('#hand').innerHTML = 'Enable hand detection';
}
});
document.body.querySelector('#hideHand').addEventListener('click', async () => {
hideHand = !hideHand;
if (hideFace) {
document.body.querySelector('#hideHand').innerHTML = 'Show hand detection';
} else {
document.body.querySelector('#hideHand').innerHTML = 'Hide hand detection';
}
});
document.body.querySelector('#handDetectionDelegate').addEventListener('change', async () => {
handDetectionDelegate = document.getElementById('handDetectionDelegate').value;
if (handTrackingEnabled) {
createHandLandmarker();
}
});
document.body.querySelector('#minHandDetectionConfidence').addEventListener('change', async () => {
minHandDetectionConfidence = parseInt(document.getElementById('minHandDetectionConfidence').value);
document.getElementById('minHandDetectionConfidenceValue').innerHTML = minHandDetectionConfidence;
if (handTrackingEnabled) {
createHandLandmarker();
}
});
document.body.querySelector('#minHandPresenceConfidence').addEventListener('change', async () => {
minHandPresenceConfidence = parseInt(document.getElementById('minHandPresenceConfidence').value);
document.getElementById('minHandPresenceConfidenceValue').innerHTML = minHandPresenceConfidence;
if (handTrackingEnabled) {
createHandLandmarker();
}
});
document.body.querySelector('#minHandTrackingConfidence').addEventListener('change', async () => {
minHandTrackingConfidence = parseInt(document.getElementById('minHandTrackingConfidence').value);
document.getElementById('minHandTrackingConfidenceValue').innerHTML = minHandTrackingConfidence;
if (handTrackingEnabled) {
createHandLandmarker();
}
});
// -------------- Pose Detection --------------
let poseDetectionDelegate = 'GPU';
let minPoseDetectionConfidence = 0.5;
document.getElementById('minPoseDetectionConfidenceValue').innerHTML = minPoseDetectionConfidence;
let minPosePresenceConfidence = 0.5;
document.getElementById('minPosePresenceConfidenceValue').innerHTML = minPosePresenceConfidence;
let minPoseTrackingConfidence = 0.5;
document.getElementById('minPoseTrackingConfidenceValue').innerHTML = minPoseTrackingConfidence;
let poseTrackingEnabled = false;
let hidepose = false;
let poseLandmarker;
let resultsPose;
document.body.querySelector('#pose').addEventListener('click', async () => {
poseTrackingEnabled = !poseTrackingEnabled;
if (poseTrackingEnabled) {
createPoseLandmarker();
document.body.querySelector('#pose').innerHTML = 'Disable pose detection';
} else {
poseLandmarker = null;
document.body.querySelector('#pose').innerHTML = 'Enable pose detection';
}
});
document.body.querySelector('#hidePose').addEventListener('click', async () => {
hidepose = !hidepose;
if (hideFace) {
document.body.querySelector('#hidePose').innerHTML = 'Show pose detection';
} else {
document.body.querySelector('#hidePose').innerHTML = 'Hide pose detection';
}
});
document.body.querySelector('#poseDetectionDelegate').addEventListener('change', async () => {
poseDetectionDelegate = document.getElementById('faceDetectionDelegate').value;
if (poseTrackingEnabled) {
createPoseLandmarker();
}
});
document.body.querySelector('#minPoseDetectionConfidence').addEventListener('change', async () => {
minPoseDetectionConfidence = parseInt(document.getElementById('minPoseDetectionConfidence').value);
document.getElementById('minPoseDetectionConfidenceValue').innerHTML = minPoseDetectionConfidence;
if (poseTrackingEnabled) {
createPoseLandmarker();
}
});
document.body.querySelector('#minPosePresenceConfidence').addEventListener('change', async () => {
minPosePresenceConfidence = parseInt(document.getElementById('minPosePresenceConfidence').value);
document.getElementById('minPosePresenceConfidenceValue').innerHTML = minPosePresenceConfidence;
if (poseTrackingEnabled) {
createPoseLandmarker();
}
});
document.body.querySelector('#minPoseTrackingConfidence').addEventListener('change', async () => {
minPoseTrackingConfidence = parseInt(document.getElementById('minPoseTrackingConfidence').value);
document.getElementById('minPoseTrackingConfidenceValue').innerHTML = minPoseTrackingConfidence;
if (poseTrackingEnabled) {
createPoseLandmarker();
}
});
// -------------- Camera --------------
function getDevices() {
// AFAICT in Safari this only gets default devices until gUM is called :/
return navigator.mediaDevices.enumerateDevices();
}
function gotDevices(deviceInfos) {
window.deviceInfos = deviceInfos; // make available to console
for (const deviceInfo of deviceInfos) {
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'videoinput') {
option.text = deviceInfo.label || `Camera ${videoSelect.length + 1}`;
videoSelect.appendChild(option);
}
}
}
function getStream() {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
const videoSource = videoSelect.value;
const constraints = {
video: {
deviceId: videoSource ? { exact: videoSource } : undefined,
frameRate: { min: 30, ideal: cameraFPS, max: 60 },
width: cameraWidth,
height: cameraHeight
}
};
return navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
}
function gotStream(stream) {
window.stream = stream; // make stream available to console
videoSelect.selectedIndex = [...videoSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
videoElement.srcObject = stream;
videoElement.addEventListener('loadeddata', predictWebcam);
}
function handleError(error) {
console.error('Error: ', error);
}
// -------------- Tracking --------------
async function createFaceLandmarker() {
const filesetResolver = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, {
baseOptions: {
modelAssetPath: 'face_landmarker_model/face_landmarker.task',
delegate: faceDetectionDelegate
},
minFaceDetectionConfidence: minFaceDetectionConfidence,
minFacePresenceConfidence: minFacePresenceConfidence,
minTrackingConfidence: minFaceTrackingConfidence,
outputFaceBlendshapes: true,
runningMode: 'VIDEO',
numFaces: 1
});
window.requestAnimationFrame(predictWebcam);
}
const createHandLandmarker = async () => {
const vision = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: 'hand_landmarker_model/hand_landmarker.task',
delegate: handDetectionDelegate
},
minHandDetectionConfidence: minHandDetectionConfidence,
minHandPresenceConfidence: minHandPresenceConfidence,
minTrackingConfidence: minHandTrackingConfidence,
runningMode: 'VIDEO',
numHands: 2
});
window.requestAnimationFrame(predictWebcam);
};
const createPoseLandmarker = async () => {
const vision = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
poseLandmarker = await PoseLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: 'pose_landmarker_model/pose_landmarker_heavy.task',
delegate: poseDetectionDelegate
},
minPoseDetectionConfidence: minPoseDetectionConfidence,
minPosePresenceConfidence: minPosePresenceConfidence,
minTrackingConfidence: minPoseTrackingConfidence,
runningMode: 'VIDEO',
numPoses: 1
});
window.requestAnimationFrame(predictWebcam);
};
// -------------- Detection --------------
function mediapipeRunning() {
if (faceLandmarker || handLandmarker || poseLandmarker) {
return true;
}
return false;
}
function predictWebcam() {
if (!mediapipeRunning()) {
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
return;
}
if (!hideFace || !hidepose || !hideHand) {
canvasElement.width = videoElement.videoWidth;
canvasElement.height = videoElement.videoHeight;
} else {
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
}
// Now let's start detecting the stream.
const startTimeMs = performance.now();
if (lastVideoTime !== videoElement.currentTime) {
lastVideoTime = videoElement.currentTime;
resultsFace = faceLandmarker ? faceLandmarker.detectForVideo(videoElement, startTimeMs) : null;
// resultsHands = handLandmarker ? handLandmarker.detectForVideo(videoElement, startTimeMs) : null;
// resultsPose = poseLandmarker ? poseLandmarker.detectForVideo(videoElement, startTimeMs) : null;
}
if (faceTrackingEnabled && resultsFace && resultsFace.faceLandmarks) {
// console.log(resultsFace);
drawDebugFace(resultsFace);
}
if (handTrackingEnabled && resultsHands && resultsHands.landmarks) {
// drawDebugHands(resultsHands);
}
if (poseTrackingEnabled && resultsPose && resultsPose.landmarks) {
// drawDebugPose(resultsPose);
}
// drawBlendShapes(videoBlendShapes, resultsFace.faceBlendshapes);
// canvasCtx.restore();
// Call this function again to keep predicting when the browser is ready.
if (cameraRunning === true) {
window.requestAnimationFrame(predictWebcam);
}
}
function drawDebugFace(results) {
if (results) {
detections = results;
}
if (hideFace) {
return;
}
for (const landmarks of results.faceLandmarks) {
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_TESSELATION, { color: '#C0C0C070', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, { color: '#FF3030', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYEBROW, { color: '#FF3030', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, { color: '#30FF30', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_EYEBROW, { color: '#30FF30', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, { color: '#E0E0E0', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LIPS, { color: '#E0E0E0', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, { color: '#FF3030', lineWidth: 1 });
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, { color: '#30FF30', lineWidth: 1 });
}
}
function drawDebugHands(results) {
if (hideHand) {
return;
}
for (const landmarks of results.landmarks) {
drawingUtils.drawConnectors(landmarks, HandLandmarker.HAND_CONNECTIONS, {
color: '#E0E0E0',
lineWidth: 1
});
drawingUtils.drawLandmarks(landmarks, { color: '#E0E0E0', lineWidth: 1 });
}
}
function drawDebugPose(results) {
if (hidePose) {
return;
}
for (const landmark of results.landmarks) {
drawingUtils.drawLandmarks(landmark, {
radius: data => DrawingUtils.lerp(data.from.z, -0.15, 0.1, 5, 1)
});
drawingUtils.drawConnectors(landmark, PoseLandmarker.POSE_CONNECTIONS, { color: '#E0E0E0', lineWidth: 1 });
}
}
function drawBlendShapes(el, blendShapes) {
if (!blendShapes.length) {
return;
}
// console.log(blendShapes[0]);
let htmlMaker = '';
blendShapes[0].categories.map(shape => {
htmlMaker += `
<li class="blend-shapes-item">
<span class="blend-shapes-label">${shape.displayName || shape.categoryName}</span>
<span class="blend-shapes-value" style="width: calc(${+shape.score * 100}% - 120px)">${(+shape.score).toFixed(4)}</span>
</li>
`;
});
el.innerHTML = htmlMaker;
}

View file

@ -1,75 +0,0 @@
{
"shapes": [
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 15,
"fill_O": 100,
"stroke_H": 263.0718090188531,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152
]
},
{
"fill_H": 116.38012093685805,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 234.7247860532549,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
151,
50,
280,
151,
9,
330,
101,
9,
151,
361
]
}
]
}

View file

@ -1,448 +0,0 @@
{
"shapes": [
{
"fill_H": 230,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 25,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
14,
87,
178,
88,
95,
78,
191,
80,
81,
82,
13,
12,
11,
0,
164,
2,
94,
19,
1,
4,
5,
195,
197,
6,
122,
245,
244,
243,
112,
26,
22,
23,
24,
110,
226,
247,
30,
29,
27,
28,
56,
190,
243,
244,
245,
122,
6,
168,
8,
9,
151,
10,
109,
67,
103,
54,
21,
162,
127,
234,
93,
132,
58,
172,
136,
150,
149,
176,
148,
152,
175,
199,
200,
18,
17,
16,
15,
14
]
},
{
"fill_H": 75,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
152,
175,
199,
200,
18,
17,
16,
15,
14,
317,
402,
318,
324,
308,
415,
310,
311,
312,
13,
12,
11,
0,
164,
2,
94,
19,
1,
4,
5,
195,
197,
6,
351,
465,
464,
463,
341,
256,
252,
253,
254,
339,
446,
467,
260,
259,
257,
258,
286,
414,
463,
464,
465,
351,
6,
168,
8,
9,
151,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
356
]
},
{
"fill_H": 115,
"fill_S": 50,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
27,
28,
56,
190,
243,
112,
26,
22,
23,
24,
110,
226,
247,
30,
29,
27,
323
]
},
{
"fill_H": 115,
"fill_S": 50,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
257,
259,
260,
467,
446,
339,
254,
253,
252,
256,
341,
463,
414,
286,
258,
257
]
},
{
"fill_H": 355,
"fill_S": 70,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
1,
44,
220,
134,
51,
5,
281,
363,
440,
274,
1
]
},
{
"fill_H": 355,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
27,
52,
29,
27
]
},
{
"fill_H": 355,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
23,
101,
24,
23
]
},
{
"fill_H": 320,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 235,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
257,
282,
259,
257
]
},
{
"fill_H": 320,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 235,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
253,
330,
254,
253
]
},
{
"fill_H": 210,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 170,
"stroke_S": 0,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
61,
76,
62,
78,
95,
88,
178,
87,
14,
317,
402,
318,
324,
308,
292,
306,
291,
306,
292,
308,
319,
404,
315,
16,
85,
180,
89,
78,
62,
76,
61
]
},
{
"fill_H": 5,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 50,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
78,
191,
80,
81,
82,
13,
312,
311,
310,
415,
308,
303,
302,
12,
72,
73,
183,
78,
264
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": [
107,
66,
105,
63,
70
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": [
336,
296,
334,
293,
300
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,325 +0,0 @@
{
"shapes": [
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
2,
167,
92,
216,
207,
187,
147,
137,
234,
127,
162,
21,
54,
103,
67,
109,
10,
151,
9,
8,
168,
6,
197,
195,
5,
3,
196,
188,
233,
243,
190,
56,
28,
27,
225,
130,
25,
110,
24,
23,
22,
233,
188,
196,
3,
5,
4,
1,
19,
94,
2
]
},
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
2,
393,
322,
436,
427,
411,
376,
366,
454,
356,
389,
251,
284,
332,
297,
338,
10,
151,
9,
8,
168,
6,
197,
195,
5,
248,
419,
412,
453,
463,
414,
286,
258,
257,
445,
359,
255,
339,
254,
253,
252,
453,
412,
419,
248,
5,
4,
1,
19,
94,
2
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
188,
245,
189,
221,
222,
223,
46,
226,
31,
228,
229,
230,
231,
128,
233,
22,
23,
24,
110,
25,
130,
225,
27,
28,
56,
190,
243,
233,
128,
188
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
412,
465,
413,
441,
442,
276,
446,
261,
448,
449,
450,
451,
357,
453,
252,
253,
254,
339,
255,
359,
445,
257,
258,
286,
414,
463,
453,
357,
412
]
},
{
"fill_H": 70,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 30,
"stroke_O": 100,
"indices": [
19,
1,
45,
1,
275
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
355,
371,
280,
352
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
277,
329,
347,
346,
340
]
},
{
"fill_H": 225,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
47,
100,
118,
117,
111
]
},
{
"fill_H": 225,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
126,
142,
50,
123
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
168,
107,
151,
336,
168,
447
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,167 +0,0 @@
{
"shapes": [
{
"fill_H": 225,
"fill_S": 5,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 255,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 255,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
73,
180,
84,
17,
314,
404,
303,
312,
82,
73,
288
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 360,
"stroke_S": 60,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
13,
17
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 265,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
92,
73,
82,
13,
312,
303,
322
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
225,
100
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
221,
117
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
453,
452,
451,
450,
449,
448,
261
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 265,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,160 +0,0 @@
{
"shapes": [
{
"fill_H": 160,
"fill_S": 25,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
152,
170,
138,
177,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
401,
367,
395,
152
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
142,
101,
118,
31,
226,
113,
225,
224,
223,
222,
221,
189,
245,
217,
209,
142
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
429,
437,
465,
413,
441,
442,
443,
444,
445,
342,
446,
261,
347,
330,
371,
429
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
89,
81,
38,
12,
268,
311,
319,
89
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": [
60,
60,
97
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": [
290,
328,
326
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,115 +0,0 @@
{
"shapes": [
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
8,
8,
8,
55,
65,
52,
53,
70,
71,
21,
139,
143,
111,
117,
118,
126,
198,
236,
3,
195,
196,
188,
233,
23,
24,
110,
25,
130,
113,
29,
27,
28,
56,
190,
243,
233,
188,
196,
195
]
},
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
8,
285,
295,
282,
283,
300,
301,
251,
368,
372,
340,
346,
347,
355,
420,
456,
248,
195,
419,
412,
453,
253,
254,
339,
255,
359,
342,
259,
257,
258,
286,
414,
463,
453,
412,
419,
195
]
},
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,188 +0,0 @@
{
"shapes": [
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
150,
150,
212,
216,
206,
98,
97,
2,
462,
250,
459,
440,
363,
456,
399,
412,
465,
413,
441,
442,
443,
444,
445,
342,
265,
372,
264,
389,
251,
284,
332,
297,
338,
10,
109,
67,
103,
54,
21,
162,
127,
34,
143,
35,
226,
113,
225,
224,
223,
222,
221,
189,
244,
233,
232,
231,
230,
229,
228,
31,
226,
35,
143,
34,
127,
234,
93,
132,
58,
172,
136,
150
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
148,
176,
149,
150,
212,
216,
206,
98,
97,
2,
164,
0,
11,
12,
13,
82,
81,
80,
191,
78,
95,
88,
178,
87,
14,
317,
402,
318,
324,
308,
415,
310,
311,
312,
13,
12,
11,
0,
164,
2,
250,
459,
440,
363,
456,
399,
412,
465,
413,
464,
453,
452,
451,
450,
449,
448,
261,
446,
342,
265,
372,
264,
389,
397
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,206 +0,0 @@
{
"shapes": [
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
4,
45,
218,
235,
203,
205,
123,
227,
162,
21,
54,
104,
69,
108,
151,
9,
8,
168,
6,
122,
245,
244,
243,
190,
56,
28,
27,
225,
130,
25,
110,
24,
23,
22,
26,
112,
243,
244,
245,
122,
6,
197,
195,
5,
4
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
130,
46,
223,
222,
221,
189,
245,
188,
174,
114,
121,
231,
24,
23,
22,
26,
112,
243,
190,
56,
28,
27,
225,
247,
25,
130,
397
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
4,
275,
438,
455,
423,
425,
352,
447,
389,
251,
284,
333,
299,
337,
151,
9,
8,
168,
6,
351,
465,
464,
463,
414,
286,
258,
257,
445,
359,
255,
339,
254,
253,
252,
256,
341,
463,
464,
465,
351,
6,
197,
195,
5,
4
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
399,
412,
465,
413,
441,
442,
443,
276,
342,
255,
467,
445,
257,
258,
286,
414,
463,
341,
256,
252,
253,
254,
451,
350,
343,
399
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,159 +0,0 @@
{
"shapes": [
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 90,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
6,
6,
294,
64,
6
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
31,
65,
233,
31
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
465,
258,
265,
253,
465
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
0,
312,
302,
311,
303,
310,
270,
409,
291,
375,
320,
324,
404,
402,
315,
14,
85,
178,
180,
88,
91,
95,
146,
61,
185,
40,
42,
39,
81,
37,
82
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
397
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 933 KiB

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

View file

@ -1,123 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div class="select">
<div>
<h3>Camera</h3>
<label for="videoSource">Camera:</label
><select id="videoSource">
<option value="">None</option>
</select>
</div>
<div>
<label for="fname">W x H</label>
<select id="cameraResolution">
<option value="0" width="640" height="480">640 x 480 (4:3)</option>
<option value="1" width="1920" height="1080">1920 x 1080 (16:9)</option>
<option value="2" width="1280" height="720">1280 x 720 (16:9)</option>
<option value="3" width="320" height="240">320 x 240 (4:3)</option>
</select>
<label for="fname">FPS</label>
<select id="cameraFPS">
<option value="30">30</option>
<option value="60">60</option>
</select>
</div>
<div>
<button id="cameraRunning">Start camera</button>
<button id="cameraVisible">Hide Camera</button>
</div>
<div>
<h3>Face</h3>
<label>Face Inference delegate</label>
<select id="faceDetectionDelegate">
<option value="GPU">GPU</option>
<option value="CPU">CPU</option>
</select>
<div class="slidecontainer">
<label>Minimum face detection confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minFaceDetectionConfidence" />
<span id="minFaceDetectionConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum face presence confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minFacePresenceConfidence" />
<span id="minFacePresenceConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum face tracking confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minFaceTrackingConfidence" />
<span id="minFaceTrackingConfidenceValue"></span>
</div>
<button id="face">Enable face detection</button>
<button id="hideFace">Hide face detection</button>
</div>
<div>
<h3>Hands</h3>
<label>Hands Inference delegate</label>
<select id="handDetectionDelegate">
<option value="GPU">GPU</option>
<option value="CPU">CPU</option>
</select>
<div class="slidecontainer">
<label>Minimum hand detection confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minHandDetectionConfidence" />
<span id="minHandDetectionConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum hand presence confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minHandPresenceConfidence" />
<span id="minHandPresenceConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum hand tracking confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minHandTrackingConfidence" />
<span id="minHandTrackingConfidenceValue"></span>
</div>
<button id="hand">Enable hand detection</button>
<button id="hideHand">Hide hand detection</button>
</div>
<div>
<h3>Pose</h3>
<label>Pose Inference delegate</label>
<select id="poseDetectionDelegate">
<option value="GPU">GPU</option>
<option value="CPU">CPU</option>
</select>
<div class="slidecontainer">
<label>Minimum pose detection confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minPoseDetectionConfidence" />
<span id="minPoseDetectionConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum pose presence confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minPosePresenceConfidence" />
<span id="minPosePresenceConfidenceValue"></span>
</div>
<div class="slidecontainer">
<label>Minimum pose tracking confidence</label>
<input type="range" min="0" max="1" step="0.1" value="0.5" class="slider" id="minPoseTrackingConfidence" />
<span id="minPoseTrackingConfidenceValue"></span>
</div>
<button id="pose">Enable pose detection</button>
<button id="hidePose">Hide pose detection</button>
</div>
</div>
<div class="canvas-container">
<video class="camera" id="video" autoplay muted playsinline></video>
<canvas class="output_canvas"></canvas>
<main></main>
</div>
<div class="blend-shapes">
<ul class="blend-shapes-list" id="video-blend-shapes"></ul>
</div>
</body>
<!-- <script defer language="javascript" type="text/javascript" src="sketch.js"></script> -->
<script src="detection.js"></script>
</html>

View file

@ -1,75 +0,0 @@
{
"shapes": [
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 15,
"fill_O": 100,
"stroke_H": 263.0718090188531,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152
]
},
{
"fill_H": 116.38012093685805,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 234.7247860532549,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
151,
50,
280,
151,
9,
330,
101,
9,
151,
361
]
}
]
}

View file

@ -1,448 +0,0 @@
{
"shapes": [
{
"fill_H": 230,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 25,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
14,
87,
178,
88,
95,
78,
191,
80,
81,
82,
13,
12,
11,
0,
164,
2,
94,
19,
1,
4,
5,
195,
197,
6,
122,
245,
244,
243,
112,
26,
22,
23,
24,
110,
226,
247,
30,
29,
27,
28,
56,
190,
243,
244,
245,
122,
6,
168,
8,
9,
151,
10,
109,
67,
103,
54,
21,
162,
127,
234,
93,
132,
58,
172,
136,
150,
149,
176,
148,
152,
175,
199,
200,
18,
17,
16,
15,
14
]
},
{
"fill_H": 75,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
152,
175,
199,
200,
18,
17,
16,
15,
14,
317,
402,
318,
324,
308,
415,
310,
311,
312,
13,
12,
11,
0,
164,
2,
94,
19,
1,
4,
5,
195,
197,
6,
351,
465,
464,
463,
341,
256,
252,
253,
254,
339,
446,
467,
260,
259,
257,
258,
286,
414,
463,
464,
465,
351,
6,
168,
8,
9,
151,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
356
]
},
{
"fill_H": 115,
"fill_S": 50,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
27,
28,
56,
190,
243,
112,
26,
22,
23,
24,
110,
226,
247,
30,
29,
27,
323
]
},
{
"fill_H": 115,
"fill_S": 50,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
257,
259,
260,
467,
446,
339,
254,
253,
252,
256,
341,
463,
414,
286,
258,
257
]
},
{
"fill_H": 355,
"fill_S": 70,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
1,
44,
220,
134,
51,
5,
281,
363,
440,
274,
1
]
},
{
"fill_H": 355,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
27,
52,
29,
27
]
},
{
"fill_H": 355,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 0,
"indices": [
23,
101,
24,
23
]
},
{
"fill_H": 320,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 235,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
257,
282,
259,
257
]
},
{
"fill_H": 320,
"fill_S": 0,
"fill_B": 30,
"fill_O": 100,
"stroke_H": 235,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
253,
330,
254,
253
]
},
{
"fill_H": 210,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 170,
"stroke_S": 0,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
61,
76,
62,
78,
95,
88,
178,
87,
14,
317,
402,
318,
324,
308,
292,
306,
291,
306,
292,
308,
319,
404,
315,
16,
85,
180,
89,
78,
62,
76,
61
]
},
{
"fill_H": 5,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 50,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
78,
191,
80,
81,
82,
13,
312,
311,
310,
415,
308,
303,
302,
12,
72,
73,
183,
78,
264
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": [
107,
66,
105,
63,
70
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": [
336,
296,
334,
293,
300
]
},
{
"fill_H": 135,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 40,
"stroke_S": 0,
"stroke_B": 70,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,325 +0,0 @@
{
"shapes": [
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
2,
167,
92,
216,
207,
187,
147,
137,
234,
127,
162,
21,
54,
103,
67,
109,
10,
151,
9,
8,
168,
6,
197,
195,
5,
3,
196,
188,
233,
243,
190,
56,
28,
27,
225,
130,
25,
110,
24,
23,
22,
233,
188,
196,
3,
5,
4,
1,
19,
94,
2
]
},
{
"fill_H": 0,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
2,
393,
322,
436,
427,
411,
376,
366,
454,
356,
389,
251,
284,
332,
297,
338,
10,
151,
9,
8,
168,
6,
197,
195,
5,
248,
419,
412,
453,
463,
414,
286,
258,
257,
445,
359,
255,
339,
254,
253,
252,
453,
412,
419,
248,
5,
4,
1,
19,
94,
2
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
188,
245,
189,
221,
222,
223,
46,
226,
31,
228,
229,
230,
231,
128,
233,
22,
23,
24,
110,
25,
130,
225,
27,
28,
56,
190,
243,
233,
128,
188
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
412,
465,
413,
441,
442,
276,
446,
261,
448,
449,
450,
451,
357,
453,
252,
253,
254,
339,
255,
359,
445,
257,
258,
286,
414,
463,
453,
357,
412
]
},
{
"fill_H": 70,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 0,
"stroke_B": 30,
"stroke_O": 100,
"indices": [
19,
1,
45,
1,
275
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
355,
371,
280,
352
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
277,
329,
347,
346,
340
]
},
{
"fill_H": 225,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
47,
100,
118,
117,
111
]
},
{
"fill_H": 225,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
126,
142,
50,
123
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
168,
107,
151,
336,
168,
447
]
},
{
"fill_H": 0,
"fill_S": 100,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 0,
"stroke_S": 100,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,167 +0,0 @@
{
"shapes": [
{
"fill_H": 225,
"fill_S": 5,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 255,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 255,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
73,
180,
84,
17,
314,
404,
303,
312,
82,
73,
288
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 360,
"stroke_S": 60,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
13,
17
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 265,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
92,
73,
82,
13,
312,
303,
322
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
225,
100
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
221,
117
]
},
{
"fill_H": 345,
"fill_S": 50,
"fill_B": 100,
"fill_O": 0,
"stroke_H": 130,
"stroke_S": 50,
"stroke_B": 0,
"stroke_O": 100,
"indices": [
453,
452,
451,
450,
449,
448,
261
]
},
{
"fill_H": 360,
"fill_S": 45,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 265,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,160 +0,0 @@
{
"shapes": [
{
"fill_H": 160,
"fill_S": 25,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
152,
170,
138,
177,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
401,
367,
395,
152
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
142,
101,
118,
31,
226,
113,
225,
224,
223,
222,
221,
189,
245,
217,
209,
142
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
429,
437,
465,
413,
441,
442,
443,
444,
445,
342,
446,
261,
347,
330,
371,
429
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 80,
"stroke_O": 100,
"indices": [
89,
81,
38,
12,
268,
311,
319,
89
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": [
60,
60,
97
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": [
290,
328,
326
]
},
{
"fill_H": 160,
"fill_S": 95,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 160,
"stroke_S": 50,
"stroke_B": 25,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,115 +0,0 @@
{
"shapes": [
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
8,
8,
8,
55,
65,
52,
53,
70,
71,
21,
139,
143,
111,
117,
118,
126,
198,
236,
3,
195,
196,
188,
233,
23,
24,
110,
25,
130,
113,
29,
27,
28,
56,
190,
243,
233,
188,
196,
195
]
},
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": [
8,
285,
295,
282,
283,
300,
301,
251,
368,
372,
340,
346,
347,
355,
420,
456,
248,
195,
419,
412,
453,
253,
254,
339,
255,
359,
342,
259,
257,
258,
286,
414,
463,
453,
412,
419,
195
]
},
{
"fill_H": 270,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 285,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 100,
"indices": []
}
]
}

View file

@ -1,188 +0,0 @@
{
"shapes": [
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
150,
150,
212,
216,
206,
98,
97,
2,
462,
250,
459,
440,
363,
456,
399,
412,
465,
413,
441,
442,
443,
444,
445,
342,
265,
372,
264,
389,
251,
284,
332,
297,
338,
10,
109,
67,
103,
54,
21,
162,
127,
34,
143,
35,
226,
113,
225,
224,
223,
222,
221,
189,
244,
233,
232,
231,
230,
229,
228,
31,
226,
35,
143,
34,
127,
234,
93,
132,
58,
172,
136,
150
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
148,
176,
149,
150,
212,
216,
206,
98,
97,
2,
164,
0,
11,
12,
13,
82,
81,
80,
191,
78,
95,
88,
178,
87,
14,
317,
402,
318,
324,
308,
415,
310,
311,
312,
13,
12,
11,
0,
164,
2,
250,
459,
440,
363,
456,
399,
412,
465,
413,
464,
453,
452,
451,
450,
449,
448,
261,
446,
342,
265,
372,
264,
389,
397
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,206 +0,0 @@
{
"shapes": [
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
4,
45,
218,
235,
203,
205,
123,
227,
162,
21,
54,
104,
69,
108,
151,
9,
8,
168,
6,
122,
245,
244,
243,
190,
56,
28,
27,
225,
130,
25,
110,
24,
23,
22,
26,
112,
243,
244,
245,
122,
6,
197,
195,
5,
4
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
130,
46,
223,
222,
221,
189,
245,
188,
174,
114,
121,
231,
24,
23,
22,
26,
112,
243,
190,
56,
28,
27,
225,
247,
25,
130,
397
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
4,
275,
438,
455,
423,
425,
352,
447,
389,
251,
284,
333,
299,
337,
151,
9,
8,
168,
6,
351,
465,
464,
463,
414,
286,
258,
257,
445,
359,
255,
339,
254,
253,
252,
256,
341,
463,
464,
465,
351,
6,
197,
195,
5,
4
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": [
399,
412,
465,
413,
441,
442,
443,
276,
342,
255,
467,
445,
257,
258,
286,
414,
463,
341,
256,
252,
253,
254,
451,
350,
343,
399
]
},
{
"fill_H": 155,
"fill_S": 0,
"fill_B": 100,
"fill_O": 100,
"stroke_H": 330,
"stroke_S": 50,
"stroke_B": 100,
"stroke_O": 0,
"indices": []
}
]
}

View file

@ -1,159 +0,0 @@
{
"shapes": [
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 90,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
10,
338,
297,
332,
284,
251,
389,
356,
454,
323,
361,
288,
397,
365,
379,
378,
400,
377,
152,
148,
176,
149,
150,
136,
172,
58,
132,
93,
234,
127,
162,
21,
54,
103,
67,
109,
10
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
6,
6,
294,
64,
6
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
31,
65,
233,
31
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
465,
258,
265,
253,
465
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
0,
312,
302,
311,
303,
310,
270,
409,
291,
375,
320,
324,
404,
402,
315,
14,
85,
178,
180,
88,
91,
95,
146,
61,
185,
40,
42,
39,
81,
37,
82
]
},
{
"fill_H": 40,
"fill_S": 100,
"fill_B": 0,
"fill_O": 100,
"stroke_H": 30,
"stroke_S": 100,
"stroke_B": 50,
"stroke_O": 100,
"indices": [
397
]
}
]
}

Some files were not shown because too many files have changed in this diff Show more