Compare commits
41 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d423d668e7 | |||
|
|
07462ac5af | ||
|
|
cc38338b15 | ||
|
|
a9f824a518 | ||
|
|
da985d3e42 | ||
| d4f7af6161 | |||
| bc7beab074 | |||
|
|
8a96908c48 | ||
|
|
7c135e293b | ||
|
|
8eb0bf491a | ||
|
|
ccda0a7736 | ||
|
|
0efb495339 | ||
|
|
932a21647d | ||
|
|
2f41de4fdd | ||
|
|
d83c0ad753 | ||
|
|
5cfdd498c9 | ||
|
|
bfd94da1d0 | ||
|
|
f1df6b24ed | ||
|
|
f7e3248b90 | ||
|
|
525cb6116e | ||
|
|
914cf831c4 | ||
| 76136c1808 | |||
|
|
ea61a8ec12 | ||
|
|
e76ff46e4c | ||
| 846ecb8412 | |||
|
|
93e7c8337d | ||
|
|
643d01b23d | ||
|
|
d5de27ca8b | ||
|
|
d9c9101965 | ||
|
|
34729f2a77 | ||
|
|
0b0af74bf1 | ||
|
|
a9f0290693 | ||
| 3eac99ee35 | |||
|
|
2d7beb4c94 | ||
| f0ad538cf6 | |||
|
|
47110e7c66 | ||
|
|
bfdd038385 | ||
|
|
9662defd93 | ||
|
|
237621eb11 | ||
|
|
66d8322346 | ||
|
|
039a16711f |
42
.compilerc
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"development": {
|
|
||||||
"application/javascript": {
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"env",
|
|
||||||
{
|
|
||||||
"targets": {
|
|
||||||
"electron": "8.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"transform-async-to-generator"
|
|
||||||
],
|
|
||||||
"sourceMaps": "inline"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"production": {
|
|
||||||
"application/javascript": {
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"env",
|
|
||||||
{
|
|
||||||
"targets": {
|
|
||||||
"electron": "8.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"transform-async-to-generator"
|
|
||||||
],
|
|
||||||
"sourceMaps": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
.editorconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
5
.eslintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
docs
|
||||||
|
dist
|
||||||
|
out
|
||||||
|
build
|
||||||
16
.eslintrc.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2021
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', '@electron-internal', '@electron-toolkit'],
|
||||||
|
rules: {
|
||||||
|
'space-before-function-paren': 'off',
|
||||||
|
vendorPrefix: 'off'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"airbnb-base"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"linebreak-style":"off",
|
|
||||||
"indent":["error", "tab"],
|
|
||||||
"no-tabs":"off",
|
|
||||||
"prefer-destructuring": ["error", {
|
|
||||||
"AssignmentExpression": {
|
|
||||||
"array": false,
|
|
||||||
"object": true
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"no-console": ["error", { "allow": ["warn", "error"] }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
.gitignore
vendored
|
|
@ -93,3 +93,21 @@ out/
|
||||||
|
|
||||||
#custom files
|
#custom files
|
||||||
src/config/settings.ini
|
src/config/settings.ini
|
||||||
|
speech_to_text_models/*
|
||||||
|
!speech_to_text_models/Where to get STT models.txt
|
||||||
|
build/
|
||||||
|
language_detection_model/*
|
||||||
|
!language_detection_model/Where to get language detection model.txt
|
||||||
|
.vscode/
|
||||||
|
package-lock.json
|
||||||
|
src/sounds/tts/*
|
||||||
|
loquendoBot_backend.spec
|
||||||
|
forge.config.js
|
||||||
|
backend/*
|
||||||
|
!backend/loquendoBot_backend.py
|
||||||
|
backend/loquendoBot_backend.exe
|
||||||
|
src/config/twitch-emotes.json
|
||||||
|
dist/*
|
||||||
|
src/config/betterttv-emotes.json
|
||||||
|
test.py
|
||||||
|
src/config/settings.json
|
||||||
|
|
|
||||||
5
.prettierrc.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
singleQuote: true
|
||||||
|
semi: true
|
||||||
|
printWidth: 140
|
||||||
|
trailingComma: none
|
||||||
|
arrowParens: avoid
|
||||||
3
.vscode/settings.json
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"npm-scripts.showStartNotification": false
|
|
||||||
}
|
|
||||||
291
backend/loquendoBot_backend.py
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
from flask import Flask, Response, jsonify, request
|
||||||
|
import gevent
|
||||||
|
|
||||||
|
import re
|
||||||
|
import gevent.monkey
|
||||||
|
import json
|
||||||
|
from waitress import serve
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("waitress")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
gevent.monkey.patch_all()
|
||||||
|
# import gevent.queue
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import pyttsx3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
import queue
|
||||||
|
import sys
|
||||||
|
import sounddevice as sd
|
||||||
|
|
||||||
|
import fasttext
|
||||||
|
|
||||||
|
from deep_translator import (
|
||||||
|
MyMemoryTranslator,
|
||||||
|
)
|
||||||
|
|
||||||
|
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||||
|
|
||||||
|
# global variables
|
||||||
|
|
||||||
|
SetLogLevel(-1)
|
||||||
|
|
||||||
|
settings = None;
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
settingsPath = os.path.normpath(sys.argv[1])
|
||||||
|
|
||||||
|
environment = sys.argv[2]
|
||||||
|
|
||||||
|
q = queue.Queue()
|
||||||
|
|
||||||
|
# gobal functions
|
||||||
|
|
||||||
|
def loadSettings():
|
||||||
|
with open(settingsPath, 'r') as file:
|
||||||
|
global settings
|
||||||
|
settings = json.load(file)
|
||||||
|
|
||||||
|
# classes
|
||||||
|
class LanguageDetection:
|
||||||
|
def __init__(self):
|
||||||
|
if environment == "dev":
|
||||||
|
settings_folder = os.path.dirname(settingsPath)
|
||||||
|
src_folder = os.path.dirname(settings_folder)
|
||||||
|
main_folder = os.path.dirname(src_folder)
|
||||||
|
language_detection_model = os.path.join(
|
||||||
|
main_folder, "language_detection_model", f"lid.176.bin"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
resources_folder = os.path.dirname(settingsPath)
|
||||||
|
language_detection_model = os.path.join(
|
||||||
|
resources_folder, "language_detection_model", f"lid.176.bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
language_detection_model = rf"{language_detection_model}"
|
||||||
|
self.model = fasttext.load_model(language_detection_model)
|
||||||
|
|
||||||
|
def predict_lang(self, text):
|
||||||
|
predictions = self.model.predict(text, k=3) # returns top 2 matching languages
|
||||||
|
language_codes = []
|
||||||
|
for prediction in predictions[0]:
|
||||||
|
language_codes.append(prediction.replace("__label__", ""))
|
||||||
|
|
||||||
|
return language_codes
|
||||||
|
|
||||||
|
|
||||||
|
class STT:
|
||||||
|
samplerate = None
|
||||||
|
args = ""
|
||||||
|
remaining = ""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
settings.read(settingsPath)
|
||||||
|
device_info = sd.query_devices(int(settings["STT"]["MICROPHONE"]), "input")
|
||||||
|
self.samplerate = int(device_info["default_samplerate"])
|
||||||
|
|
||||||
|
if environment == "dev":
|
||||||
|
settings_folder = os.path.dirname(settingsPath)
|
||||||
|
src_folder = os.path.dirname(settings_folder)
|
||||||
|
main_folder = os.path.dirname(src_folder)
|
||||||
|
vosk_model = os.path.join(
|
||||||
|
main_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
resources_folder = os.path.dirname(settingsPath)
|
||||||
|
vosk_model = os.path.join(
|
||||||
|
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.model = Model(rf"{vosk_model}")
|
||||||
|
self.dump_fn = None
|
||||||
|
|
||||||
|
self.q = gevent.queue.Queue()
|
||||||
|
self.rec = None
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
def callback(self, indata, frames, time, status):
|
||||||
|
if status:
|
||||||
|
print(status, file=sys.stderr)
|
||||||
|
self.q.put(bytes(indata))
|
||||||
|
|
||||||
|
def start_recognition(self):
|
||||||
|
self.is_running = True
|
||||||
|
|
||||||
|
with sd.RawInputStream(
|
||||||
|
samplerate=self.samplerate,
|
||||||
|
blocksize=8000,
|
||||||
|
device=0, # Default microphone
|
||||||
|
dtype="int16",
|
||||||
|
channels=1,
|
||||||
|
callback=self.callback,
|
||||||
|
):
|
||||||
|
self.rec = KaldiRecognizer(self.model, self.samplerate)
|
||||||
|
while True:
|
||||||
|
data = self.q.get()
|
||||||
|
if self.rec.AcceptWaveform(data):
|
||||||
|
result = self.rec.Result()
|
||||||
|
result_json = json.loads(str(result))
|
||||||
|
yield f"data: {result_json}\n\n"
|
||||||
|
else:
|
||||||
|
partialResult = self.rec.PartialResult()
|
||||||
|
result_json = json.loads(str(partialResult))
|
||||||
|
yield f"data: {result_json}\n\n"
|
||||||
|
|
||||||
|
def stop_recognition(self):
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
loadSettings()
|
||||||
|
if settings["STT"]["USE_STT"] and settings["STT"]["LANGUAGE"] != '':
|
||||||
|
speech_recognition_service = STT()
|
||||||
|
|
||||||
|
|
||||||
|
class TTS:
|
||||||
|
engine = None
|
||||||
|
rate = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.engine = pyttsx3.init()
|
||||||
|
|
||||||
|
def say(self, message, voice, count):
|
||||||
|
voices = self.engine.getProperty("voices")
|
||||||
|
for item in voices:
|
||||||
|
if item.name == voice:
|
||||||
|
matching_id = item.id
|
||||||
|
break
|
||||||
|
self.engine.setProperty("voice", matching_id)
|
||||||
|
|
||||||
|
settings_folder = os.path.dirname(settingsPath)
|
||||||
|
if environment == "dev":
|
||||||
|
src_folder = os.path.dirname(settings_folder)
|
||||||
|
bot_folder = os.path.dirname(src_folder)
|
||||||
|
saveLocation = os.path.join(
|
||||||
|
bot_folder, "sounds", f"Internal_{count}.mp3"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
saveLocation = os.path.join(
|
||||||
|
settings_folder, "sounds", f"Internal_{count}.mp3"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.engine.save_to_file(message, saveLocation)
|
||||||
|
self.engine.runAndWait()
|
||||||
|
|
||||||
|
def voices(self):
|
||||||
|
voices = self.engine.getProperty("voices")
|
||||||
|
self.engine.say(
|
||||||
|
""
|
||||||
|
) # engine breaks if you do not say something after getting voices
|
||||||
|
self.engine.runAndWait()
|
||||||
|
|
||||||
|
return [voice.name for voice in voices]
|
||||||
|
|
||||||
|
loadSettings()
|
||||||
|
if settings["TTS"]["USE_TTS"]:
|
||||||
|
text_to_speech_service = TTS()
|
||||||
|
|
||||||
|
# endpoints
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/stream", methods=["GET"])
|
||||||
|
def stream_recognition():
|
||||||
|
def generate():
|
||||||
|
return speech_recognition_service.start_recognition()
|
||||||
|
|
||||||
|
return Response(generate(), content_type="text/event-stream")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/stop", methods=["POST"])
|
||||||
|
def stop_recording():
|
||||||
|
speech_recognition_service.stop_recognition()
|
||||||
|
return Response("Speech recognition stopped", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/terminate", methods=["GET"])
|
||||||
|
def terminate_processes():
|
||||||
|
shutdown_server()
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown_server():
|
||||||
|
func = request.environ.get("sever shutdown")
|
||||||
|
if func is None:
|
||||||
|
raise RuntimeError("Server is not running")
|
||||||
|
func()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/status", methods=["GET"])
|
||||||
|
def server_status():
|
||||||
|
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"])
|
||||||
|
def trigger_backend_event():
|
||||||
|
try:
|
||||||
|
request_data = request.json
|
||||||
|
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")
|
||||||
|
count = request_data.get("count")
|
||||||
|
text_to_speech_service.say(filteredMessage, voice, count)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": e}), 500
|
||||||
|
return jsonify({"message": "Audio triggered"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/voices", methods=["GET"])
|
||||||
|
def get_voices():
|
||||||
|
try:
|
||||||
|
voices = text_to_speech_service.voices()
|
||||||
|
return jsonify({"voices": voices}), 200
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": e}), 500
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
loadSettings()
|
||||||
|
port = int(settings["GENERAL"]["PORT"])
|
||||||
|
else:
|
||||||
|
environment = "dev"
|
||||||
|
port = 9000
|
||||||
|
stream_recognition()
|
||||||
|
|
||||||
|
serve(app, host="0.0.0.0", port=port)
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
packagerConfig: {
|
|
||||||
icon: './src/images/icon.ico',
|
|
||||||
asar: true,
|
|
||||||
"extraResource": [
|
|
||||||
"./src/config/loquendo.db",
|
|
||||||
"./src/sounds"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
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: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
https://fasttext.cc/docs/en/language-identification.html
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 Khyretis
|
Copyright (c) 2021 Khyretos
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
7077
package-lock.json
generated
120
package.json
|
|
@ -1,48 +1,78 @@
|
||||||
{
|
{
|
||||||
"name": "loquendo-bot",
|
"name": "loquendo-bot",
|
||||||
"version": "2.1.0",
|
"productName": "LoquendoBot",
|
||||||
"description": "Bot assistant for streamers over different platforms",
|
"version": "2.6.0",
|
||||||
"main": "src/main.js",
|
"description": "Bot assistant for streamers over different platforms",
|
||||||
"scripts": {
|
"main": "src/main.js",
|
||||||
"start": "electron-forge start",
|
"scripts": {
|
||||||
"package": "electron-forge package",
|
"start": "electron-forge start",
|
||||||
"make": "electron-forge make",
|
"build": "npm run backend && electron-builder",
|
||||||
"publish": "electron-forge publish",
|
"publish": "electron-forge publish",
|
||||||
"lint": "echo \"No linting configured\""
|
"backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./backend/loquendoBot_backend.py"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "LoquendoBot",
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
],
|
||||||
|
"icon": "./src/images/icon.ico"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"nsis": {
|
||||||
"author": {
|
"oneClick": false,
|
||||||
"name": "Khyretos",
|
"installerIcon": "./src/images/icon.ico",
|
||||||
"email": "khyretos@gmail.com"
|
"uninstallerIcon": "./src/images/icon.ico",
|
||||||
|
"uninstallDisplayName": "LoquendoBot-uninstaller",
|
||||||
|
"license": "license.md",
|
||||||
|
"allowToChangeInstallationDirectory": "true"
|
||||||
},
|
},
|
||||||
"license": "ISC",
|
"extraResources": [
|
||||||
"dependencies": {
|
"speech_to_text_models/Where to get STT models.txt",
|
||||||
"axios": "^1.4.0",
|
"backend/loquendoBot_backend.exe",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"language_detection_model",
|
||||||
"express": "^4.18.2",
|
"sounds"
|
||||||
"franc": "^6.1.0",
|
]
|
||||||
"i18next-electron-language-detector": "^0.0.10",
|
},
|
||||||
"ini": "^2.0.0",
|
"keywords": [],
|
||||||
"langdetect": "^0.2.1",
|
"author": {
|
||||||
"node-google-tts-api": "^1.1.1",
|
"name": "Khyretos",
|
||||||
"querystring": "^0.2.1",
|
"email": "khyretos@gmail.com"
|
||||||
"request": "^2.88.2",
|
},
|
||||||
"say": "^0.16.0",
|
"license": "ISC",
|
||||||
"socket.io": "^4.7.1",
|
"dependencies": {
|
||||||
"socket.io-client": "^4.7.1",
|
"@mediapipe/tasks-vision": "^0.10.12",
|
||||||
"sound-play": "^1.1.0",
|
"axios": "^1.4.0",
|
||||||
"tmi.js": "^1.8.5",
|
"dlivetv-api": "^1.0.10",
|
||||||
"url": "^0.11.1",
|
"emoji-picker-element": "^1.21.0",
|
||||||
"winston": "^3.10.0",
|
"express": "^4.18.2",
|
||||||
"write-ini-file": "^4.0.1"
|
"flag-icons": "^7.1.0",
|
||||||
},
|
"ini": "^2.0.0",
|
||||||
"devDependencies": {
|
"kill-process-by-name": "^1.0.5",
|
||||||
"@electron-forge/cli": "^6.2.1",
|
"node-google-tts-api": "^1.1.1",
|
||||||
"@electron-forge/maker-deb": "^6.2.1",
|
"p5": "^1.9.2",
|
||||||
"@electron-forge/maker-rpm": "^6.2.1",
|
"querystring": "^0.2.1",
|
||||||
"@electron-forge/maker-squirrel": "^6.2.1",
|
"socket.io": "^4.7.1",
|
||||||
"@electron-forge/maker-zip": "^6.2.1",
|
"socket.io-client": "^4.7.1",
|
||||||
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
|
"sockette": "^2.0.6",
|
||||||
"electron": "25.4.0"
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,5 +45,6 @@ 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
|
||||||
|
|
|
||||||
3
speech_to_text_models/Where to get STT models.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Download the model from here: https://alphacephei.com/vosk/models unzip it
|
||||||
|
and drop the folder in the 'speech_to_text_models' folder. Restart the app
|
||||||
|
to load the changes.
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
EN
|
|
||||||
ES
|
|
||||||
NL
|
|
||||||
737
src/css/chat.css
|
|
@ -1,493 +1,580 @@
|
||||||
@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);
|
||||||
margin-left: 50px;
|
padding-left: 50px;
|
||||||
margin-right: 50px;
|
padding-right: 50px;
|
||||||
font-family: 'FRAMDCN';
|
font-family: 'FRAMDCN';
|
||||||
|
position: relative;
|
||||||
|
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 .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 .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: 0 0 2px rgba(0, 0, 0, .12), 0 2px 4px rgba(0, 0, 0, .24);
|
box-shadow:
|
||||||
|
0 0 2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input input[good]+button:hover {
|
.chat-input input[good] + button:hover {
|
||||||
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
box-shadow:
|
||||||
/* filter: brightness(150%); */
|
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 {
|
.chat-input input[good] + button path {
|
||||||
fill: var(--chat-bubble-message);
|
fill: var(--chat-bubble-message);
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-container {
|
.msg-container {
|
||||||
position: relative;
|
direction: ltr;
|
||||||
display: inline-block;
|
position: static;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 0 10px 0;
|
padding: 10px 0px 0px 0px;
|
||||||
padding: 0;
|
display: grid;
|
||||||
|
grid-template: 1fr / 1fr;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container > * {
|
||||||
|
grid-column: 1 / 1;
|
||||||
|
grid-row: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container.sender {
|
||||||
|
place-items: self-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container.user {
|
||||||
|
place-items: self-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-box {
|
.msg-box {
|
||||||
display: flex;
|
background: var(--chat-bubble);
|
||||||
background: var(--chat-bubble);
|
color: white;
|
||||||
padding: 5px 5px 5px 5px;
|
min-width: 100px;
|
||||||
border-radius: 6px 6px 6px 6px;
|
border-radius: 5px;
|
||||||
margin-left: -20px;
|
padding: 18px 5px 5px 5px;
|
||||||
margin-right: 10px;
|
box-shadow:
|
||||||
margin-top: 10px;
|
0 0 2px rgba(0, 0, 0, 0.12),
|
||||||
max-width: 80%;
|
0 2px 4px rgba(0, 0, 0, 0.24);
|
||||||
width: auto;
|
width: fit-content;
|
||||||
float: left;
|
position: relative;
|
||||||
word-wrap: break-word;
|
align-self: start;
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, .12), 0 2px 4px rgba(0, 0, 0, .24);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-box-user {
|
.msg-box.sender {
|
||||||
display: flex;
|
margin: 25px 25px 0px 35px;
|
||||||
background: var(--chat-bubble);
|
}
|
||||||
padding: 5px 5px 5px 5px;
|
|
||||||
border-radius: 6px 6px 6px 6px;
|
.msg-box.user {
|
||||||
margin-right: -20px;
|
text-align: left;
|
||||||
margin-top: 10px;
|
margin: 25px 35px 0px 0px;
|
||||||
max-width: 80%;
|
|
||||||
width: auto;
|
|
||||||
float: right;
|
|
||||||
word-wrap: break-word;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, .12), 0 2px 4px rgba(0, 0, 0, .24);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
border-radius: 50%;
|
position: relative;
|
||||||
height: 40px;
|
border-radius: 50%;
|
||||||
width: 40px;
|
height: 50px;
|
||||||
margin: 0 10px 10px 0;
|
width: 50px;
|
||||||
|
z-index: 5;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-img-user {
|
.messages.user {
|
||||||
display: inline-block;
|
margin-right: 20px;
|
||||||
border-radius: 50%;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
margin: 0 0px 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flr {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: calc(100% - 50px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
margin-left: 20px;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-user {
|
|
||||||
margin-right: 20px;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg {
|
.msg {
|
||||||
font-size: 11pt;
|
font-size: 12pt;
|
||||||
line-height: 13pt;
|
color: var(--chat-bubble-message);
|
||||||
color: var(--chat-bubble-message);
|
margin: 0 0 0 0;
|
||||||
margin: 0 0 4px 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: self-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
||||||
float: left;
|
background-color: var(--main-color4);
|
||||||
color: var(--chat-bubble-header);
|
color: white;
|
||||||
font-weight: bold;
|
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 {
|
.username-temp {
|
||||||
color: var(--chat-bubble-header-temp);
|
color: var(--chat-bubble-header-temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-time {
|
.post-time {
|
||||||
float: right;
|
font-size: 8pt;
|
||||||
font-weight: bold;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-self .msg-box {
|
.post-time.user {
|
||||||
border-radius: 6px 6px 6px 6px;
|
padding: 5px 15px 5px 5px;
|
||||||
background: var(--main-color1);
|
margin: 0px 50px 0px 0px;
|
||||||
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 {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container-user {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
position: relative;
|
|
||||||
float: right;
|
|
||||||
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;
|
||||||
height: 20px;
|
border-radius: 50%;
|
||||||
border-radius: 50%;
|
z-index: 6;
|
||||||
bottom: 0;
|
position: relative;
|
||||||
right: 0;
|
align-self: start;
|
||||||
margin-left: -20px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-circle-user {
|
.status-circle.sender {
|
||||||
width: 20px;
|
margin-left: 40px;
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-left: -50px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
.status-circle.user {
|
||||||
font-size: 100%;
|
margin-right: 40px;
|
||||||
padding: 10px;
|
}
|
||||||
padding-right: 40px;
|
|
||||||
outline: none;
|
.menu-select {
|
||||||
-webkit-appearance: none;
|
font-size: 0.9rem;
|
||||||
-moz-appearance: none;
|
height: 40px;
|
||||||
background: transparent;
|
border-radius: 20px;
|
||||||
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-color: var(--main-color3);
|
||||||
background-repeat: no-repeat;
|
color: var(--main-color2);
|
||||||
background-position-x: 95%;
|
align-items: center;
|
||||||
background-position-y: 5px;
|
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 {
|
.AdvancedMenu {
|
||||||
border: 1px var(--main-color2) solid;
|
border: 1px var(--main-color2) solid;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
width: 300px;
|
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;
|
||||||
|
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 {
|
||||||
|
font-size: 12pt;
|
||||||
|
padding-right: 5px;
|
||||||
|
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 */
|
||||||
|
|
||||||
|
|
||||||
/* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
317
src/css/home.css
|
|
@ -1,245 +1,274 @@
|
||||||
/* Basic styling */
|
/* Basic styling */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--main-color1: #6e2c8c;
|
overflow: hidden;
|
||||||
--main-color1-temp: #6e2c8c;
|
--main-color1: #6e2c8c;
|
||||||
/*Left bar and top right bar*/
|
--main-color1-temp: #6e2c8c;
|
||||||
--main-color2: white;
|
/*Left bar and top right bar*/
|
||||||
--main-color2-temp: white;
|
--main-color2: white;
|
||||||
/*Icons and text*/
|
--main-color2-temp: white;
|
||||||
--main-color3: #211E1E;
|
/*Icons and text*/
|
||||||
--main-color3-temp: #211E1E;
|
--main-color3: #211e1e;
|
||||||
/*Buttons and input*/
|
--main-color3-temp: #211e1e;
|
||||||
--main-color4: #2f2c34;
|
/*Buttons and input*/
|
||||||
--main-color4-temp: #2f2c34;
|
--main-color4: #2f2c34;
|
||||||
--top-bar: #100B12;
|
--main-color4-temp: #2f2c34;
|
||||||
--top-bar-temp: #100B12;
|
--top-bar: #100b12;
|
||||||
--mid-section: #352d3d;
|
--top-bar-temp: #100b12;
|
||||||
--mid-section-temp: #352d3d;
|
--mid-section: #352d3d;
|
||||||
--chat-bubble: #7A6D7F;
|
--mid-section-temp: #352d3d;
|
||||||
--chat-bubble-temp: #7A6D7F;
|
--chat-bubble: #7a6d7f;
|
||||||
--chat-bubble-header: #141414;
|
--chat-bubble-header: #141414;
|
||||||
--chat-bubble-header-temp: #141414;
|
--chat-bubble-username: white;
|
||||||
--chat-bubble-message: white;
|
--chat-bubble-message: white;
|
||||||
--chat-bubble-message-temp: white;
|
--chat-bubble-temp: #7a6d7f;
|
||||||
|
--chat-bubble-header-temp: #141414;
|
||||||
|
--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 {
|
||||||
font-family: "Segoe UI", sans-serif;
|
height: 100%;
|
||||||
background: transparent;
|
margin: 0;
|
||||||
}
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar {
|
#titlebar {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
}
|
|
||||||
|
|
||||||
#titlebar #drag-region {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 138px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
||||||
|
|
||||||
#window-controls {
|
|
||||||
-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 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 32px;
|
||||||
|
text-align: -webkit-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-selector {
|
.language-selector {
|
||||||
-webkit-app-region: no-drag;
|
position: absolute;
|
||||||
position: absolute;
|
-webkit-app-region: no-drag;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont,
|
font-family:
|
||||||
'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
'NotoColorEmojiLimited',
|
||||||
'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';
|
||||||
|
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: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont,
|
font-family:
|
||||||
'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
'NotoColorEmojiLimited',
|
||||||
'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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
background-color: lightblue !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warn {
|
.warn {
|
||||||
background-color: yellow;
|
background-color: #f39c12 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: lightcoral;
|
background-color: #e74c3c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logTable {
|
#logTable {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#Logs {
|
#Logs {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
343
src/css/menu.css
|
|
@ -1,226 +1,303 @@
|
||||||
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
|
box-shadow:
|
||||||
transition: .3s ease-in-out;
|
rgba(0, 0, 0, 0.16) 0px 3px 6px,
|
||||||
|
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: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
|
box-shadow:
|
||||||
transition: .3s ease-in-out;
|
rgba(0, 0, 0, 0.16) 0px 3px 6px,
|
||||||
|
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: 1;
|
z-index: 2;
|
||||||
transition: .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: .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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,184 +1,195 @@
|
||||||
/*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: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
;
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress2::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
;
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress3::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
;
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress4::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
;
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*mozilla*/
|
/*mozilla*/
|
||||||
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: #1a1a1a;
|
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: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress2::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress2::-moz-range-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress3::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress3::-moz-range-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=range].styled-slider.slider-progress4::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress4::-moz-range-track {
|
||||||
background: linear-gradient(#7b2cbf, #7b2cbf) 0/var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*ms*/
|
/*ms*/
|
||||||
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: #1a1a1a;
|
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: #7b2cbf;
|
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: #7b2cbf;
|
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: #7b2cbf;
|
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: #7b2cbf;
|
background: var(--main-color1);
|
||||||
border: none;
|
border: none;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputBox {
|
.inputBox {
|
||||||
border: none;
|
border: none;
|
||||||
width: 50px;
|
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;
|
||||||
margin-left: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
548
src/css/tabs.css
|
|
@ -1,431 +1,419 @@
|
||||||
.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;
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
font-size: .9rem;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: var(--main-color3);
|
|
||||||
color: var(--main-color2);
|
|
||||||
align-items: center;
|
|
||||||
border: 0px;
|
|
||||||
padding-left: 20px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.language {
|
.language {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
margin-left: 10px;
|
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="lol"] {
|
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: 300px;
|
width: 260px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-left: 10px;
|
margin-left: 10px;
|
||||||
padding-right: 40px;
|
}
|
||||||
/* To make space for the reveal button */
|
|
||||||
|
input[type='lol'] {
|
||||||
|
background: var(--main-color3);
|
||||||
|
border: none;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 40px;
|
||||||
|
width: 300px;
|
||||||
|
outline: none;
|
||||||
|
color: var(--main-color2);
|
||||||
|
font-size: 10pt;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 40px;
|
||||||
|
/* 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: #555;
|
color: var(--main-color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#toasts {
|
#toasts {
|
||||||
position: fixed;
|
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: 50%;
|
right: 0%;
|
||||||
/* Center the toasts horizontally */
|
/* Center the toasts horizontally */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
z-index: 999;
|
||||||
/* Center the toasts horizontally */
|
|
||||||
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: toastAnimation 0.5s ease-in-out forwards, toastDisappear 0.5s ease-in-out 9s forwards;
|
animation:
|
||||||
|
toastAnimation 0.5s ease-in-out forwards,
|
||||||
|
toastDisappear 0.5s ease-in-out 9s forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply different colors based on the toast type */
|
/* Apply different colors based on the toast type */
|
||||||
.toast.info {
|
.info {
|
||||||
background-color: #3498db;
|
background-color: lightblue !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.success {
|
.success {
|
||||||
background-color: #2ecc71;
|
background-color: #2ecc71 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.warning {
|
.warning {
|
||||||
background-color: #f39c12;
|
background-color: #f39c12 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.error {
|
.error {
|
||||||
background-color: #e74c3c;
|
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;
|
||||||
display: inline-block;
|
font-size: 12pt;
|
||||||
visibility: hidden;
|
padding: 5px;
|
||||||
font-size: 12px;
|
background: var(--main-color3);
|
||||||
line-height: 20px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
visibility: hidden;
|
||||||
background: var(--main-color3);
|
box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 5px;
|
color: var(--main-color2);
|
||||||
visibility: hidden;
|
font-family: 'xxii_avenmedium';
|
||||||
opacity: 1;
|
z-index: 999;
|
||||||
box-shadow: -2px 2px 5px rgba(0, 0, 0, 0.2);
|
max-width: 200px;
|
||||||
transition: opacity 0.3s, visibility 0s;
|
width: max-content;
|
||||||
color: var(--main-color2);
|
|
||||||
font-family: 'xxii_avenmedium';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .tooltip .tooltiptext {
|
div[type='text']:disabled {
|
||||||
width: 120px;
|
background: #4b4b4b;
|
||||||
background-color: black;
|
display: none;
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip .tooltiptext::after {
|
input[type='text']:disabled {
|
||||||
content: "";
|
background: #4b4b4b;
|
||||||
margin-left: -5px;
|
|
||||||
border-width: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: black transparent transparent transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip:hover .tooltiptext {
|
button[type='text']:disabled {
|
||||||
visibility: visible;
|
background: #4b4b4b;
|
||||||
} */
|
display: none;
|
||||||
|
|
||||||
input[type=text]:disabled {
|
|
||||||
background: #dddddd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type2=text]:disabled {
|
input[type2='text']:disabled {
|
||||||
background: #dddddd;
|
background: #4b4b4b;
|
||||||
}
|
|
||||||
|
|
||||||
button[type=text]:disabled {
|
|
||||||
background: #dddddd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div:disabled {
|
div:disabled {
|
||||||
background: #dddddd;
|
background: #4b4b4b;
|
||||||
filter: brightness(200%);
|
filter: brightness(200%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div:disabled {
|
||||||
|
background: #4b4b4b;
|
||||||
|
filter: brightness(200%);
|
||||||
|
}
|
||||||
|
|
|
||||||
88
src/css/token-autocomplete.css
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
@ -1,134 +1,163 @@
|
||||||
#tstx {
|
#tstx {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: 40px;
|
margin-left: 40px;
|
||||||
margin-left: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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);
|
||||||
width: 50px;
|
scale: 0.75;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: 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;
|
||||||
}
|
|
||||||
|
|
||||||
#TTSTest {
|
|
||||||
width: 296px;
|
|
||||||
height: 85px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
font-size: 14pt;
|
height: 60px;
|
||||||
resize: none;
|
padding: 5px;
|
||||||
background: var(--main-color3);
|
width: 300px;
|
||||||
color: var(--main-color2);
|
resize: none;
|
||||||
font-family: 'xxii_avenmedium';
|
border-radius: 5px;
|
||||||
border: none;
|
background: var(--main-color3);
|
||||||
outline: none;
|
color: var(--main-color2);
|
||||||
border-radius: 5px;
|
font-family: 'xxii_avenmedium';
|
||||||
|
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;
|
||||||
margin-top: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.SmallButton {
|
.SmallButton {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
width: 50px;
|
width: 50px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-shadow: 0 0 5px #070607, 0 0 5px #070607, 0 0 5px #070607;
|
text-shadow:
|
||||||
/* transition: all 0.15s ease-in-out; */
|
0 0 5px #070607,
|
||||||
text-align: center;
|
0 0 5px #070607,
|
||||||
|
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);
|
||||||
width: 50px;
|
cursor: pointer;
|
||||||
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: box-shadow 0.3s ease, background-color 0.3s ease;
|
transition:
|
||||||
/* Add a smooth transition for box-shadow and background-color */
|
box-shadow 0.3s ease,
|
||||||
|
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;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AdvancedMenuIcon2 {
|
||||||
|
align-items: flex-start;
|
||||||
|
margin: auto;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
select:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
label:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-right:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
.circle-left:hover {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,84 +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);
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-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; */
|
||||||
|
/* float: right; */
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
src/images/amazon.png
Normal file
|
After Width: | Height: | Size: 787 B |
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="24" height="24px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
|
|
||||||
<title>Amazon-color</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<defs>
|
|
||||||
|
|
||||||
</defs>
|
|
||||||
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="Color-" transform="translate(-601.000000, -560.000000)">
|
|
||||||
<g id="Amazon" transform="translate(601.000000, 560.000000)">
|
|
||||||
<path d="M25.4026553,25.9595294 C24.660417,27.4418824 23.3876054,28.3962353 22.0103725,28.7181176 C21.8015298,28.7181176 21.4826213,28.8225882 21.1637129,28.8225882 C18.835399,28.8225882 17.458166,27.0211765 17.458166,24.3727059 C17.458166,20.9788235 19.4703937,19.392 22.0103725,18.6465882 C23.3876054,18.3303529 24.9793255,18.2230588 26.5682233,18.2230588 L26.5682233,19.4964706 C26.5682233,21.9331765 26.6726447,23.8390588 25.4026553,25.9595294 L25.4026553,25.9595294 Z M26.5682233,13.3524706 C25.1909904,13.4569412 23.5992703,13.5614118 22.0103725,13.7703529 C19.574815,14.0922353 17.1392576,14.5157647 15.1298521,15.4701176 C11.2098182,17.0597647 8.55977364,20.4508235 8.55977364,25.4287059 C8.55977364,31.6856471 12.5842289,34.8621176 17.6726531,34.8621176 C19.3659723,34.8621176 20.7432053,34.6475294 22.0103725,34.3341176 C24.0282445,33.696 25.7187415,32.5298824 27.7309692,30.4094118 C28.8965372,31.9990588 29.2182679,32.7444706 31.2276733,34.4385882 C31.7582467,34.6475294 32.28882,34.6475294 32.7093276,34.3341176 C33.9821392,33.2724706 36.208854,31.3637647 37.3715998,30.3049412 C37.9021732,29.8814118 37.7977518,29.2432941 37.4760212,28.7181176 C36.3132753,27.2329412 35.1448851,25.9595294 35.1448851,23.0992941 L35.1448851,13.5614118 C35.1448851,9.53505882 35.4666157,5.82494118 32.5004849,3.072 C30.0649275,0.849882353 26.2493149,0 23.2831841,0 L22.0103725,0 C16.6115064,0.313411765 10.8937319,2.64564706 9.61809814,9.32329412 C9.40643324,10.1731765 10.0442501,10.4894118 10.4675799,10.5938824 L16.3998415,11.3364706 C17.0348362,11.2291765 17.3537447,10.6983529 17.458166,10.1731765 C17.9859172,7.84094118 19.8937235,6.67482353 22.0103725,6.46023529 L22.4365245,6.46023529 C23.7093361,6.46023529 25.086569,6.99105882 25.8259851,8.05270588 C26.6726447,9.32329412 26.5682233,11.0202353 26.5682233,12.5054118 L26.5682233,13.3524706 L26.5682233,13.3524706 Z" fill="#343B45">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
<path d="M47.9943556,35.9463529 L47.9943556,35.9435294 C47.971778,35.4437647 47.8673567,35.0625882 47.658514,34.7463529 L47.6359364,34.7152941 L47.6105366,34.6842353 C47.3988717,34.4527059 47.1956734,34.3651765 46.9755419,34.2691765 C46.3179696,34.0150588 45.3612442,33.8795294 44.2097872,33.8767059 C43.382883,33.8767059 42.4713128,33.9557647 41.5540982,34.1562353 L41.551276,34.0941176 L40.6284171,34.4018824 L40.6114839,34.4103529 L40.0893771,34.5797647 L40.0893771,34.6023529 C39.47696,34.8564706 38.9209869,35.1727059 38.4045245,35.5482353 C38.0827939,35.7882353 37.8175072,36.1072941 37.8033962,36.5957647 C37.7949296,36.8611765 37.9303952,37.1661176 38.1533489,37.3468235 C38.3763025,37.5275294 38.6359448,37.5896471 38.8645429,37.5896471 C38.9181647,37.5896471 38.9689643,37.5868235 39.0141194,37.5783529 L39.0592746,37.5755294 L39.093141,37.5698824 C39.5446928,37.4738824 40.2022651,37.4089412 40.9727253,37.3016471 C41.6331198,37.2282353 42.3330251,37.1745882 42.9397978,37.1745882 C43.368772,37.1717647 43.7554132,37.2028235 44.0206999,37.2592941 C44.1533432,37.2875294 44.2521202,37.3214118 44.3057419,37.3496471 C44.3254973,37.3552941 44.3396083,37.3637647 44.3480749,37.3694118 C44.3593637,37.4061176 44.3762969,37.5021176 44.3734747,37.6348235 C44.3791191,38.1430588 44.164632,39.0861176 43.8683012,40.0065882 C43.5804369,40.9270588 43.2304843,41.8503529 42.999064,42.4630588 C42.94262,42.6042353 42.9059314,42.7595294 42.9059314,42.9289412 C42.900287,43.1745882 43.0018862,43.4738824 43.2163733,43.6715294 C43.425216,43.8691765 43.696147,43.9482353 43.9219229,43.9482353 L43.9332117,43.9482353 C44.2718756,43.9454118 44.5597398,43.8098824 44.8080933,43.6150588 C47.1505182,41.5087059 47.9661336,38.1430588 48,36.2484706 L47.9943556,35.9463529 Z M41.0489247,38.8658824 C40.8090378,38.8630588 40.5635065,38.9195294 40.3349084,39.0268235 C40.0780883,39.1284706 39.8156239,39.2470588 39.5672704,39.3515294 L39.2032068,39.504 L38.7290774,39.6931765 L38.7290774,39.6988235 C33.5785648,41.7882353 28.16841,43.0136471 23.1618295,43.1209412 C22.9783866,43.1265882 22.7921215,43.1265882 22.614323,43.1265882 C14.7403887,43.1322353 8.31706456,39.4785882 1.83729642,35.8785882 C1.61152053,35.76 1.37727804,35.6978824 1.15150215,35.6978824 C0.860815683,35.6978824 0.561662624,35.808 0.344353327,36.0112941 C0.12704403,36.2174118 -0.00277710907,36.5138824 4.50895989e-05,36.816 C-0.00277710907,37.2084706 0.208887791,37.5698824 0.505218651,37.8042353 C6.58705678,43.0870588 13.25309,47.9943529 22.2192152,48 C22.3941915,48 22.57199,47.9943529 22.7497885,47.9915294 C28.453452,47.8644706 34.902176,45.936 39.9087564,42.7905882 L39.9398006,42.7708235 C40.5945507,42.3783529 41.2493008,41.9322353 41.8673623,41.4381176 C42.2511813,41.1529412 42.516468,40.7068235 42.516468,40.2437647 C42.4995348,39.4221176 41.8024517,38.8658824 41.0489247,38.8658824 Z" id="Fill-237" fill="#FF9A00">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB |
BIN
src/images/dlive-icon.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
src/images/dlive.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
src/images/google.png
Normal file
|
After Width: | Height: | Size: 571 B |
|
|
@ -1,28 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="24px" height="24px" viewBox="-0.5 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
|
|
||||||
<title>Google-color</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<defs>
|
|
||||||
|
|
||||||
</defs>
|
|
||||||
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="Color-" transform="translate(-401.000000, -860.000000)">
|
|
||||||
<g id="Google" transform="translate(401.000000, 860.000000)">
|
|
||||||
<path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
<path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EB4335">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
<path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
<path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4">
|
|
||||||
|
|
||||||
</path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
src/images/icon-256.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/images/icon-512.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 136 KiB |
BIN
src/images/server.png
Normal file
|
After Width: | Height: | Size: 327 B |
|
|
@ -1,24 +0,0 @@
|
||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
width="24px" height="24px" viewBox="0 0 491.52 491.52" xml:space="preserve">
|
|
||||||
<path style="fill:#64798A;" d="M400.903,0H90.615c-7.296,0-13.209,5.914-13.209,13.209v465.102c0,7.294,5.913,13.209,13.209,13.209
|
|
||||||
h310.288c7.296,0,13.21-5.914,13.21-13.209V13.209C414.114,5.914,408.199,0,400.903,0z"/>
|
|
||||||
<path style="fill:#3A556A;" d="M141.749,283.578h208.024c12.942,0,23.467-10.525,23.467-23.467V0.001h-8.936v260.11
|
|
||||||
c0,8.012-6.519,14.53-14.531,14.53H141.749c-8.012,0-14.531-6.518-14.531-14.53V0.001h-8.936v260.11
|
|
||||||
C118.281,273.052,128.806,283.578,141.749,283.578z"/>
|
|
||||||
<circle style="fill:#EBF0F3;" cx="241.971" cy="341.146" r="21.852"/>
|
|
||||||
<circle style="fill:#44C4A1;" cx="241.971" cy="380.16" r="7.752"/>
|
|
||||||
<rect x="140.749" y="28.16" style="fill:#2F4859;" width="210.074" height="71.439"/>
|
|
||||||
<rect x="145.193" y="32.645" style="fill:#3A556A;" width="201.114" height="62.505"/>
|
|
||||||
<rect x="140.718" y="135.071" style="fill:#2F4859;" width="210.074" height="71.434"/>
|
|
||||||
<rect x="145.193" y="139.571" style="fill:#3A556A;" width="201.114" height="62.505"/>
|
|
||||||
<path style="fill:#D5D6DB;" d="M335.55,110.819h-32.736c-1.093,0-1.978,0.886-1.978,1.981v10.529c0,1.094,0.886,1.981,1.978,1.981
|
|
||||||
h32.736c1.093,0,1.979-0.888,1.979-1.981V112.8C337.529,111.706,336.643,110.819,335.55,110.819z"/>
|
|
||||||
<path style="fill:#EBF0F3;" d="M335.55,106.857h-32.736c-1.093,0-1.978,0.888-1.978,1.981v10.529c0,1.094,0.886,1.981,1.978,1.981
|
|
||||||
h32.736c1.093,0,1.979-0.887,1.979-1.981v-10.529C337.529,107.745,336.643,106.857,335.55,106.857z"/>
|
|
||||||
<path style="fill:#D5D6DB;" d="M335.55,218.06h-32.736c-1.093,0-1.978,0.886-1.978,1.981v10.529c0,1.094,0.886,1.981,1.978,1.981
|
|
||||||
h32.736c1.093,0,1.979-0.887,1.979-1.981v-10.529C337.529,218.946,336.643,218.06,335.55,218.06z"/>
|
|
||||||
<path style="fill:#EBF0F3;" d="M335.55,214.098h-32.736c-1.093,0-1.978,0.887-1.978,1.981v10.529c0,1.095,0.886,1.981,1.978,1.981
|
|
||||||
h32.736c1.093,0,1.979-0.886,1.979-1.981v-10.529C337.529,214.985,336.643,214.098,335.55,214.098z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
src/images/settings.png
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
src/images/sound.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
src/images/stt.png
Normal file
|
After Width: | Height: | Size: 542 B |
BIN
src/images/theme.png
Normal file
|
After Width: | Height: | Size: 420 B |
28
src/images/theme.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg height="24px" width="24px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<path style="fill:#FFC033;" d="M491.09,328.459c-10.239-17.809-28.826-27.937-51.199-27.937c-28.737,0-47.726-21.359-48.085-44.522
|
||||||
|
c-0.111-9.906,3.228-20.035,10.685-28.828c8.459-10.017,20.815-15.694,33.837-15.694c24.598,0,44.522-10.351,54.872-28.271
|
||||||
|
c9.796-17.03,9.685-38.4-0.333-55.652C445.122,48.863,360.307,0,269.373,0C146.938,0,41.421,86.15,18.381,204.911
|
||||||
|
c-3.338,17.03-5.009,34.059-4.896,51.089v0.668c0.223,58.991,20.48,116.313,58.435,162.282C120.671,478.052,192.685,512,269.374,512
|
||||||
|
c91.157,0,176.085-49.085,221.717-128C500.997,366.859,500.997,345.6,491.09,328.459z M292.745,104.181
|
||||||
|
c13.803-23.93,44.3-32.278,68.452-18.365c23.93,13.803,32.167,44.412,18.365,68.452c-13.803,23.819-44.412,32.167-68.452,18.254
|
||||||
|
C287.18,158.72,278.945,128.223,292.745,104.181z"/>
|
||||||
|
<path style="fill:#F9A926;" d="M71.919,418.95C120.67,478.052,192.684,512,269.373,512c91.157,0,176.085-49.085,221.717-128
|
||||||
|
c9.907-17.141,9.907-38.4,0-55.541c-10.239-17.809-28.826-27.937-51.199-27.937c-28.737,0-47.726-21.359-48.085-44.522H13.483v0.668
|
||||||
|
C13.707,315.659,33.963,372.981,71.919,418.95z"/>
|
||||||
|
<path style="fill:#88CC2A;" d="M124.677,306.087c-27.619,0-50.087-22.468-50.087-50.087s22.468-50.087,50.087-50.087
|
||||||
|
s50.087,22.468,50.087,50.087S152.296,306.087,124.677,306.087z"/>
|
||||||
|
<path style="fill:#736056;" d="M171.981,420.609c-23.923-13.802-32.21-44.397-18.326-68.424
|
||||||
|
c13.695-23.726,44.21-32.294,68.413-18.321c23.922,13.801,32.21,44.392,18.326,68.419
|
||||||
|
C226.622,426.154,196.056,434.511,171.981,420.609z"/>
|
||||||
|
<path style="fill:#37AFCC;" d="M323.198,431.18c-26.712-7.156-42.599-34.587-35.424-61.342c7.175-26.741,34.59-42.578,61.336-35.413
|
||||||
|
c12.924,3.456,23.728,11.745,30.413,23.326l0,0c6.696,11.587,8.478,25.087,5.011,38.016
|
||||||
|
C377.349,422.55,349.888,438.339,323.198,431.18z"/>
|
||||||
|
<path style="fill:#E6563A;" d="M153.655,159.81c-13.855-23.966-5.651-54.579,18.326-68.424
|
||||||
|
c24.013-13.841,54.599-5.613,68.413,18.332l0,0c13.877,24.016,5.607,54.605-18.326,68.424
|
||||||
|
C197.941,192.057,167.398,183.631,153.655,159.81z"/>
|
||||||
|
<path style="fill:#7FB335;" d="M124.677,306.087c27.619,0,50.087-22.468,50.087-50.087H74.59
|
||||||
|
C74.59,283.619,97.057,306.087,124.677,306.087z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/images/translate.png
Normal file
|
After Width: | Height: | Size: 549 B |
BIN
src/images/trovo-icon.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/images/trovo.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src/images/tts.png
Normal file
|
After Width: | Height: | Size: 501 B |
BIN
src/images/twitch.png
Normal file
|
After Width: | Height: | Size: 436 B |
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="24px" height="24px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
|
|
||||||
|
|
||||||
<path fill="#ffffff" d="M13 7.5l-2 2H9l-1.75 1.75V9.5H5V2h8v5.5z"/>
|
|
||||||
|
|
||||||
<g fill="#9146FF">
|
|
||||||
|
|
||||||
<path d="M4.5 1L2 3.5v9h3V15l2.5-2.5h2L14 8V1H4.5zM13 7.5l-2 2H9l-1.75 1.75V9.5H5V2h8v5.5z"/>
|
|
||||||
|
|
||||||
<path d="M11.5 3.75h-1v3h1v-3zM8.75 3.75h-1v3h1v-3z"/>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 474 B |
BIN
src/images/youtube-icon.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src/images/youtube.png
Normal file
|
After Width: | Height: | Size: 175 B |
1621
src/index.html
192
src/js/amazon.js
|
|
@ -1,95 +1,119 @@
|
||||||
|
/* 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');
|
||||||
|
|
||||||
class PollyTTS {
|
function getAmazonVoices() {
|
||||||
constructor(credentials) {
|
if (!settings.AMAZON.USE_AMAZON) {
|
||||||
this.credentials = credentials;
|
callback();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
textToSpeech(options, callback) {
|
addVoiceService('Amazon');
|
||||||
if (!options) {
|
|
||||||
return callback(new Error('Options are missing'));
|
|
||||||
}
|
|
||||||
const qs = {
|
|
||||||
Text: options.text,
|
|
||||||
TextType: options.textType || 'text',
|
|
||||||
VoiceId: options.voiceId || 'Vicki',
|
|
||||||
SampleRate: options.sampleRate || 22050,
|
|
||||||
OutputFormat: options.outputFormat || 'mp3',
|
|
||||||
};
|
|
||||||
const opts = {
|
|
||||||
service: 'polly',
|
|
||||||
region: options.region || 'eu-west-1',
|
|
||||||
path: `/v1/speech?${querystring.stringify(qs)}`,
|
|
||||||
signQuery: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// you can also pass AWS credentials in explicitly (otherwise taken from process.env)
|
const primaryVoice = document.querySelector('#primaryAmazonVoice');
|
||||||
aws4.sign(opts, this.credentials);
|
const secondaryVoice = document.querySelector('#secondaryAmazonVoice');
|
||||||
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;
|
function setVoicesinSelect(voiceSelect) {
|
||||||
}
|
const voices = Object.values(amazonVoices);
|
||||||
|
voices.forEach(voice => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.classList.add('option');
|
||||||
|
|
||||||
describeVoices(options, callback) {
|
option.value = voice;
|
||||||
if (!options) {
|
option.innerHTML = voice;
|
||||||
return callback(new Error('Options are missing'));
|
|
||||||
}
|
|
||||||
const qs = {};
|
|
||||||
|
|
||||||
if (options.languageCode) {
|
voiceSelect.appendChild(option);
|
||||||
qs.LanguageCode = options.languageCode;
|
});
|
||||||
}
|
}
|
||||||
|
setVoicesinSelect(primaryVoice);
|
||||||
if (options.nextToken) {
|
primaryVoice.value = settings.AMAZON.PRIMARY_VOICE;
|
||||||
qs.NextToken = options.nextToken;
|
setVoicesinSelect(secondaryVoice);
|
||||||
}
|
secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE;
|
||||||
|
|
||||||
const opts = {
|
|
||||||
service: 'polly',
|
|
||||||
region: options.region || 'eu-west-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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PollyTTS;
|
if (settings.AMAZON.USE_AMAZON) {
|
||||||
|
getAmazonVoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
class PollyTTS {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
describeVoices(callback, credentials) {
|
||||||
|
this.credentials = credentials;
|
||||||
|
const qs = {
|
||||||
|
Engine: 'neural'
|
||||||
|
};
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
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();
|
||||||
|
module.exports = pollyTTS;
|
||||||
|
|
|
||||||
130
src/js/auth.js
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
/* global settings,twitch,trovo, fs, settingsPath, ini, shell, options, axios */
|
||||||
|
|
||||||
|
const twitchAuthentication = () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
const http = require('http');
|
||||||
|
const redirectUri = 'http://localhost:1989/auth';
|
||||||
|
const scopes = ['chat:edit', 'chat:read', 'user:read:follows', 'user:read:subscriptions', 'channel:read:redemptions'];
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const tempAuthServer = express();
|
||||||
|
const port = 1989;
|
||||||
|
|
||||||
|
const { parse: parseQueryString } = require('querystring');
|
||||||
|
|
||||||
|
tempAuthServer.use(function (req, res, next) {
|
||||||
|
console.log('1');
|
||||||
|
if (req.url !== '/auth') {
|
||||||
|
console.log(req.url);
|
||||||
|
const token = parseQueryString(req.query.auth);
|
||||||
|
console.log(req);
|
||||||
|
console.log(token);
|
||||||
|
settings.TWITCH.OAUTH_TOKEN = token['#access_token'];
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
|
|
||||||
|
resolve(token['#access_token']);
|
||||||
|
stopServer();
|
||||||
|
} else {
|
||||||
|
res.send(htmlString);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
||||||
|
<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(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTwitchOauthToken() {
|
||||||
|
return twitchAuthentication().then(res => {
|
||||||
|
twitch.getTwitchUserId();
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const trovoAuthentication = () =>
|
||||||
|
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 };
|
||||||
447
src/js/backend.js
Normal file
|
|
@ -0,0 +1,447 @@
|
||||||
|
/* global settings, resourcesPath, sound, twitch, getLanguageProperties, addSingleTooltip, showChatMessage, languageObject, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */
|
||||||
|
|
||||||
|
const spawn = require('child_process').spawn;
|
||||||
|
const kill = require('kill-process-by-name');
|
||||||
|
let python;
|
||||||
|
|
||||||
|
async function getInstalledVoices() {
|
||||||
|
if (!settings.TTS.USE_TTS) {
|
||||||
|
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) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryVoice = document.querySelector('#primaryVoice');
|
||||||
|
const secondaryVoice = document.querySelector('#secondaryVoice');
|
||||||
|
|
||||||
|
function setVoicesinSelect(voiceSelect) {
|
||||||
|
const voices = Object.values(internalVoices.voices);
|
||||||
|
voices.forEach(voice => {
|
||||||
|
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 {
|
||||||
|
message.message = 'Error, Could not translate message';
|
||||||
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
|
return getTranslatedMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseData.code === 429) {
|
||||||
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
|
setTranslatedMessage({
|
||||||
|
originalMessage: message.message,
|
||||||
|
translation: 'Rate limit exceeded, please change translation service.',
|
||||||
|
messageId: message.messageId,
|
||||||
|
language: message.language,
|
||||||
|
formattedMessage: message.formattedMessage,
|
||||||
|
username: message.username,
|
||||||
|
logoUrl: message.logoUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
|
setTranslatedMessage({
|
||||||
|
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() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' });
|
||||||
|
if (response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log('Status:', responseData);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSTT() {
|
||||||
|
const eventSource = new EventSource('http://127.0.0.1:9000/stream');
|
||||||
|
|
||||||
|
eventSource.addEventListener('message', event => {
|
||||||
|
const result = event.data;
|
||||||
|
console.log(result); // Log the received data
|
||||||
|
});
|
||||||
|
|
||||||
|
eventSource.addEventListener('error', event => {
|
||||||
|
console.error('EventSource failed:', event);
|
||||||
|
|
||||||
|
eventSource.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
eventSource.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getInternalTTSAudio(requestData) {
|
||||||
|
ttsRequestCount++;
|
||||||
|
requestData.count = ttsRequestCount;
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST', // HTTP method
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json' // Specify the content type
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestData) // 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}/audio`, requestOptions);
|
||||||
|
if (response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log('Audio:', responseData);
|
||||||
|
return ttsRequestCount;
|
||||||
|
} else {
|
||||||
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createBackendServer = () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
if (main.isPackaged) {
|
||||||
|
python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']);
|
||||||
|
} else {
|
||||||
|
python = spawn('python', ['-u', path.join(resourcesPath, '../backend/loquendoBot_backend.py'), settingsPath, 'dev']);
|
||||||
|
}
|
||||||
|
// Capture the stdout of the Python process
|
||||||
|
python.stdout.on('data', data => {
|
||||||
|
console.info(`${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Capture the stderr of the Python process
|
||||||
|
python.stderr.on('data', data => {
|
||||||
|
// console.error(`${data}`);
|
||||||
|
if (data.toString().startsWith('INFO:waitress:Serving on')) {
|
||||||
|
resolve('finished');
|
||||||
|
} else {
|
||||||
|
console.error(`${data}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for the Python process to exit
|
||||||
|
python.on('close', code => {
|
||||||
|
console.log(`Python process exited with code ${code}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof python.pid !== 'number') {
|
||||||
|
console.log('failed');
|
||||||
|
} else {
|
||||||
|
// console.log(`Spawned subprocess correctly!, PID = ${python.pid}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initiateBackend() {
|
||||||
|
try {
|
||||||
|
createBackendServer().then(() => {
|
||||||
|
getBackendServerStatus();
|
||||||
|
getInstalledVoices();
|
||||||
|
if (settings.STT.USE_STT && !settings.STT.LANGUAGE === '') {
|
||||||
|
startSTT();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during backend initialization:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initiateBackend();
|
||||||
|
|
||||||
|
// TODO: convert to restartServer function
|
||||||
|
ipcRenderer.on('quit-event', async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' });
|
||||||
|
if (response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log('Response:', responseData);
|
||||||
|
kill('loquendoBot_backend');
|
||||||
|
} else {
|
||||||
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
|
kill('loquendoBot_backend');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
kill('loquendoBot_backend');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { getInternalTTSAudio, getDetectedLanguage, getTranslatedMessage };
|
||||||
262
src/js/chat.js
|
|
@ -1,46 +1,94 @@
|
||||||
function getResponse() {
|
/* global messageTemplates,getLanguageProperties, backend, messageId emojiPicker, settings, getPostTime, showChatMessage, twitch */
|
||||||
const userText = document.querySelector('#textInput').value;
|
|
||||||
|
|
||||||
// If nothing is written don't do anything
|
async function getResponse() {
|
||||||
if (userText === '') {
|
const userText = document.querySelector('#textInput').value;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create chat message from received data
|
// If nothing is written don't do anything
|
||||||
const article = document.createElement('article');
|
if (userText === '') {
|
||||||
article.className = 'msg-container msg-self';
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
article.innerHTML = messageTemplates.userTemplate;
|
messageId++;
|
||||||
|
|
||||||
const postTime = article.querySelector('.post-time');
|
// Create chat message from received data
|
||||||
if (postTime) {
|
const article = document.createElement('article');
|
||||||
postTime.innerText = getPostTime();
|
article.setAttribute('id', messageId);
|
||||||
}
|
article.className = 'msg-container user';
|
||||||
|
|
||||||
const msg = article.querySelector('.msg');
|
article.innerHTML = messageTemplates.userTemplate;
|
||||||
if (msg) {
|
|
||||||
msg.innerText = userText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the message to the main chat box (shows the message)
|
const userImg = article.querySelector('.user-img');
|
||||||
showChatMessage(article);
|
if (userImg) {
|
||||||
|
userImg.src = settings.TWITCH.USER_LOGO_URL;
|
||||||
|
}
|
||||||
|
|
||||||
twitch.sendMessage(userText);
|
const postTime = article.querySelector('.post-time');
|
||||||
|
|
||||||
// Empty input box after sending message
|
if (postTime) {
|
||||||
document.body.querySelector('#textInput').value = '';
|
postTime.innerText = getPostTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
article.appendChild(postTime);
|
||||||
|
|
||||||
|
const msg = article.querySelector('.msg-box');
|
||||||
|
if (msg) {
|
||||||
|
await replaceChatMessageWithCustomEmojis(userText).then(data => {
|
||||||
|
msg.innerHTML = data;
|
||||||
|
|
||||||
|
// Appends the message to the main chat box (shows the message)
|
||||||
|
showChatMessage(article);
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -49,131 +97,99 @@ 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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// right panel
|
|
||||||
document.body.querySelector('.circle-right').addEventListener('click', () => {
|
|
||||||
const menu = document.body.querySelector('.sidepanel-right');
|
|
||||||
|
|
||||||
if (menu.classList.contains('collapse-menu-right')) {
|
|
||||||
menu.classList.remove('collapse-menu-right');
|
|
||||||
} else {
|
|
||||||
menu.classList.add('collapse-menu-right');
|
|
||||||
}
|
|
||||||
|
|
||||||
const leftCircle = document.body.querySelector('.circle-right');
|
|
||||||
|
|
||||||
if (leftCircle.classList.contains('collapse-circle-right')) {
|
|
||||||
leftCircle.classList.remove('collapse-circle-right');
|
|
||||||
} else {
|
|
||||||
leftCircle.classList.add('collapse-circle-right');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Show panels
|
// #region Show panels
|
||||||
|
|
||||||
// TODO: animate Option panels
|
// TODO: animate Option panels
|
||||||
// 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('click', (event) => {
|
btn.addEventListener(
|
||||||
event.stopPropagation();
|
'click',
|
||||||
panels.forEach((el) => {
|
event => {
|
||||||
if (el === panel) return;
|
event.stopPropagation();
|
||||||
el.classList.remove('show');
|
panels.forEach(el => {
|
||||||
});
|
if (el === panel) return;
|
||||||
if (panel.classList.contains('show')) {
|
el.classList.remove('show');
|
||||||
// panel.classList.remove('show');
|
});
|
||||||
} else {
|
if (!panel.classList.contains('show')) {
|
||||||
panel.classList.add('show');
|
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', '#BrowsersourceChat', '#btnBrowsersourceChat');
|
displayPanel('.OptionPanel', '#BrowsersourceChatBubble', '#btnBrowsersourceChatBubble');
|
||||||
displayPanel('.OptionPanel', '#BrowsersourceVtuber', '#btnBrowsersourceVtuber');
|
displayPanel('.OptionPanel', '#BrowsersourceVtuber', '#btnBrowsersourceVtuber');
|
||||||
displayPanel('.OptionPanel', '#TTS', '#btnTTS');
|
displayPanel('.OptionPanel', '#BrowsersourcePNGTuber', '#btnBrowsersourcePNGTuber');
|
||||||
|
displayPanel('.OptionPanel', '#FaceMask', '#btnFaceMask');
|
||||||
displayPanel('.OptionPanel', '#Chat', '#btnChat');
|
displayPanel('.OptionPanel', '#Chat', '#btnChat');
|
||||||
|
displayPanel('.OptionPanel', '#ThemeCreator', '#btnThemeCreator');
|
||||||
|
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('click', (event) => {
|
btn.addEventListener(
|
||||||
event.stopPropagation();
|
'click',
|
||||||
panels.forEach((el) => {
|
event => {
|
||||||
if (el === panel) return;
|
event.stopPropagation();
|
||||||
el.classList.remove('item-active');
|
panels.forEach(el => {
|
||||||
});
|
if (el === panel) return;
|
||||||
if (panel.classList.contains('item-active')) {
|
el.classList.remove('item-active');
|
||||||
// panel.classList.remove('item-active');
|
});
|
||||||
} else {
|
if (!panel.classList.contains('item-active')) {
|
||||||
panel.classList.add('item-active');
|
panel.classList.add('item-active');
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
capture: true,
|
{
|
||||||
});
|
capture: true
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
displayPanelX('.item', '#btnTTS', '#btnTTS');
|
|
||||||
displayPanelX('.item', '#btnChat', '#btnChat');
|
displayPanelX('.item', '#btnChat', '#btnChat');
|
||||||
displayPanelX('.item', '#btnBrowsersourceChat', '#btnBrowsersourceChat');
|
displayPanelX('.item', '#btnBrowsersourceChatBubble', '#btnBrowsersourceChatBubble');
|
||||||
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', '#btnChatCreator', '#btnChatCreator');
|
||||||
// #region Show/Hide Advanced Menu
|
|
||||||
document.body.querySelector('#ShowAdvancedMenu').addEventListener('click', () => {
|
|
||||||
document.getElementById('AdvancedMenu_mask').style.visibility = 'visible';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.querySelector('#HideAdvancedMenu').addEventListener('click', () => {
|
|
||||||
document.getElementById('AdvancedMenu_mask').style.visibility = 'hidden';
|
|
||||||
});
|
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Show/Hide Theme Creator
|
// #region Show/Hide Theme Creator
|
||||||
document.body.querySelector('#ShowThemeCreator').addEventListener('click', () => {
|
|
||||||
document.getElementById('ThemeCreator_mask').style.visibility = 'visible';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.querySelector('#HideThemeCreator').addEventListener('click', () => {
|
|
||||||
document.getElementById('ThemeCreator_mask').style.visibility = 'hidden';
|
|
||||||
});
|
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Test/Save TTS
|
module.exports = {
|
||||||
document.body.querySelector('#TTSTestButton').addEventListener('click', () => {
|
replaceChatMessageWithCustomEmojis
|
||||||
const text = document.getElementById('TTSTest').value;
|
};
|
||||||
sound.playVoice(text, '', 'User', text);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
102
src/js/dlive.js
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* global settings, addVoiceService, googleVoices */
|
||||||
|
|
||||||
|
function getGoogleVoices() {
|
||||||
|
if (!settings.GOOGLE.USE_GOOGLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addVoiceService('Google');
|
||||||
|
|
||||||
|
const primaryVoice = document.querySelector('#primaryGoogleVoice');
|
||||||
|
const secondaryVoice = document.querySelector('#secondaryGoogleVoice');
|
||||||
|
|
||||||
|
function setVoicesinSelect(voiceSelect) {
|
||||||
|
const voices = Object.values(googleVoices);
|
||||||
|
voices.forEach(voice => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.classList.add('option');
|
||||||
|
|
||||||
|
option.value = voice;
|
||||||
|
option.innerHTML = voice;
|
||||||
|
|
||||||
|
voiceSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setVoicesinSelect(primaryVoice);
|
||||||
|
primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE;
|
||||||
|
setVoicesinSelect(secondaryVoice);
|
||||||
|
secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.GOOGLE.USE_GOOGLE) {
|
||||||
|
getGoogleVoices();
|
||||||
|
}
|
||||||
334
src/js/languages.js
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
// TODO: Enable STT:
|
||||||
|
// 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
|
||||||
|
// *info page with credits, version and more info
|
||||||
|
|
||||||
|
const languages = {
|
||||||
|
none: { IETF: 'none', ISO639: 'none', ISO3166: 'xx' },
|
||||||
|
english: { IETF: 'en-GB', ISO639: 'en', ISO3166: 'gb' },
|
||||||
|
spanish: { IETF: 'es-ES', ISO639: 'es', ISO3166: 'es' },
|
||||||
|
dutch: { IETF: 'nl-NL', ISO639: 'nl', ISO3166: 'nl' },
|
||||||
|
'chinese simplified': { IETF: 'zh-CN', ISO639: 'zh', ISO3166: 'cn' },
|
||||||
|
russian: { IETF: 'ru-RU', ISO639: 'ru', ISO3166: 'ru' },
|
||||||
|
indonesian: { IETF: 'id-ID', ISO639: 'id', ISO3166: 'id' },
|
||||||
|
hindi: { IETF: 'hi-IN', ISO639: 'hi', ISO3166: 'in' },
|
||||||
|
filipino: { IETF: 'fil-PH', ISO639: 'fil', ISO3166: 'ph' },
|
||||||
|
turkish: { IETF: 'tr-TR', ISO639: 'tr', ISO3166: 'tr' },
|
||||||
|
acehnese: { IETF: 'ace-ID', ISO639: 'ace', ISO3166: 'id' },
|
||||||
|
afrikaans: { IETF: 'af-ZA', ISO639: 'af', ISO3166: 'za' },
|
||||||
|
akan: { IETF: 'ak-GH', ISO639: 'ak', ISO3166: 'gh' },
|
||||||
|
albanian: { IETF: 'sq-AL', ISO639: 'sq', ISO3166: 'al' },
|
||||||
|
amharic: { IETF: 'am-ET', ISO639: 'am', ISO3166: 'et' },
|
||||||
|
'antigua and barbuda creole english': { IETF: 'aig-AG', ISO639: 'aig', ISO3166: 'ag' },
|
||||||
|
arabic: { IETF: 'ar-SA', ISO639: 'ar', ISO3166: 'sa' },
|
||||||
|
'arabic egyptian': { IETF: 'ar-EG', ISO639: 'arz', ISO3166: 'eg' },
|
||||||
|
aragonese: { IETF: 'es-ES', ISO639: 'an', ISO3166: 'es' },
|
||||||
|
armenian: { IETF: 'hy-AM', ISO639: 'hy', ISO3166: 'am' },
|
||||||
|
assamese: { IETF: 'as-IN', ISO639: 'as', ISO3166: 'in' },
|
||||||
|
asturian: { IETF: 'ast-ES', ISO639: 'ast', ISO3166: 'es' },
|
||||||
|
'austrian german': { IETF: 'de-AT', ISO639: 'de', ISO3166: 'at' },
|
||||||
|
awadhi: { IETF: 'awa-IN', ISO639: 'awa', ISO3166: 'in' },
|
||||||
|
'ayacucho quechua': { IETF: 'quy-PE', ISO639: 'quy', ISO3166: 'pe' },
|
||||||
|
azerbaijani: { IETF: 'az-AZ', ISO639: 'az', ISO3166: 'az' },
|
||||||
|
'bahamas creole english': { IETF: 'bah-BS', ISO639: 'bah', ISO3166: 'bs' },
|
||||||
|
bajan: { IETF: 'bjs-BB', ISO639: 'bjs', ISO3166: 'bb' },
|
||||||
|
balinese: { IETF: 'ban-ID', ISO639: 'ban', ISO3166: 'id' },
|
||||||
|
'balkan gipsy': { IETF: 'rm-RO', ISO639: 'rm', ISO3166: 'ro' },
|
||||||
|
bambara: { IETF: 'bm-ML', ISO639: 'bm', ISO3166: 'ml' },
|
||||||
|
banjar: { IETF: 'bjn-ID', ISO639: 'bjn', ISO3166: 'id' },
|
||||||
|
bashkir: { IETF: 'ba-RU', ISO639: 'ba', ISO3166: 'ru' },
|
||||||
|
basque: { IETF: 'eu-ES', ISO639: 'eu', ISO3166: 'es-pv' },
|
||||||
|
belarusian: { IETF: 'be-BY', ISO639: 'be', ISO3166: 'by' },
|
||||||
|
'belgian french': { IETF: 'fr-BE', ISO639: 'fr', ISO3166: 'be' },
|
||||||
|
bemba: { IETF: 'bem-ZM', ISO639: 'bem', ISO3166: 'zm' },
|
||||||
|
bengali: { IETF: 'bn-IN', ISO639: 'bn', ISO3166: 'bd' },
|
||||||
|
bhojpuri: { IETF: 'bho-IN', ISO639: 'bho', ISO3166: 'in' },
|
||||||
|
bihari: { IETF: 'bh-IN', ISO639: 'bh', ISO3166: 'in' },
|
||||||
|
bislama: { IETF: 'bi-VU', ISO639: 'bi', ISO3166: 'vu' },
|
||||||
|
borana: { IETF: 'gax-KE', ISO639: 'gax', ISO3166: 'ke' },
|
||||||
|
bosnian: { IETF: 'bs-BA', ISO639: 'bs', ISO3166: 'ba' },
|
||||||
|
'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', ISO639: 'bs', ISO3166: 'ba' },
|
||||||
|
breton: { IETF: 'br-FR', ISO639: 'br', ISO3166: 'fr' },
|
||||||
|
buginese: { IETF: 'bug-ID', ISO639: 'bug', ISO3166: 'id' },
|
||||||
|
bulgarian: { IETF: 'bg-BG', ISO639: 'bg', ISO3166: 'bg' },
|
||||||
|
burmese: { IETF: 'my-MM', ISO639: 'my', ISO3166: 'mm' },
|
||||||
|
catalan: { IETF: 'ca-ES', ISO639: 'ca', ISO3166: 'es' },
|
||||||
|
'catalan valencian': { IETF: 'cav-ES', ISO639: 'cav', ISO3166: 'es' },
|
||||||
|
cebuano: { IETF: 'ceb-PH', ISO639: 'ceb', ISO3166: 'ph' },
|
||||||
|
'central atlas tamazight': { IETF: 'tzm-MA', ISO639: 'tzm', ISO3166: 'ma' },
|
||||||
|
'central aymara': { IETF: 'ayr-BO', ISO639: 'ayr', ISO3166: 'bo' },
|
||||||
|
'central kanuri (latin script)': { IETF: 'knc-NG', ISO639: 'knc', ISO3166: 'ng' },
|
||||||
|
'chadian arabic': { IETF: 'shu-TD', ISO639: 'shu', ISO3166: 'td' },
|
||||||
|
chamorro: { IETF: 'ch-GU', ISO639: 'ch', ISO3166: 'gu' },
|
||||||
|
cherokee: { IETF: 'chr-US', ISO639: 'chr', ISO3166: 'us' },
|
||||||
|
chhattisgarhi: { IETF: 'hne-IN', ISO639: 'hne', ISO3166: 'in' },
|
||||||
|
'chinese trad. (hong kong)': { IETF: 'zh-HK', ISO639: 'zh', ISO3166: 'hk' },
|
||||||
|
'chinese traditional': { IETF: 'zh-TW', ISO639: 'zh', ISO3166: 'tw' },
|
||||||
|
'chinese traditional macau': { IETF: 'zh-MO', ISO639: 'zh', ISO3166: 'mo' },
|
||||||
|
chittagonian: { IETF: 'ctg-BD', ISO639: 'ctg', ISO3166: 'bd' },
|
||||||
|
chokwe: { IETF: 'cjk-AO', ISO639: 'cjk', ISO3166: 'ao' },
|
||||||
|
'classical greek': { IETF: 'grc-GR', ISO639: 'grc', ISO3166: 'gr' },
|
||||||
|
'comorian ngazidja': { IETF: 'zdj-KM', ISO639: 'zdj', ISO3166: 'km' },
|
||||||
|
coptic: { IETF: 'cop-EG', ISO639: 'cop', ISO3166: 'eg' },
|
||||||
|
'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' },
|
||||||
|
'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' },
|
||||||
|
croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' },
|
||||||
|
'serbo-croatian': { IETF: 'sr-Cyrl-RS', ISO639: 'sh', ISO3166: 'sr' },
|
||||||
|
czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' },
|
||||||
|
danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' },
|
||||||
|
dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' },
|
||||||
|
dimli: { IETF: 'diq-TR', ISO639: 'diq', ISO3166: 'tr' },
|
||||||
|
dyula: { IETF: 'dyu-CI', ISO639: 'dyu', ISO3166: 'ci' },
|
||||||
|
dzongkha: { IETF: 'dz-BT', ISO639: 'dz', ISO3166: 'bt' },
|
||||||
|
'eastern yiddish': { IETF: 'ydd-US', ISO639: 'ydd', ISO3166: 'il' },
|
||||||
|
emakhuwa: { IETF: 'vmw-MZ', ISO639: 'vmw', ISO3166: 'mz' },
|
||||||
|
'english australia': { IETF: 'en-AU', ISO639: 'en', ISO3166: 'au' },
|
||||||
|
'english canada': { IETF: 'en-CA', ISO639: 'en', ISO3166: 'ca' },
|
||||||
|
'english india': { IETF: 'en-IN', ISO639: 'en', ISO3166: 'in' },
|
||||||
|
'english ireland': { IETF: 'en-IE', ISO639: 'en', ISO3166: 'ie' },
|
||||||
|
'english new zealand': { IETF: 'en-NZ', ISO639: 'en', ISO3166: 'nz' },
|
||||||
|
'english singapore': { IETF: 'en-SG', ISO639: 'en', ISO3166: 'sg' },
|
||||||
|
'english south africa': { IETF: 'en-ZA', ISO639: 'en', ISO3166: 'za' },
|
||||||
|
'english us': { IETF: 'en-US', ISO639: 'en', ISO3166: 'us' },
|
||||||
|
esperanto: { IETF: 'eo-EU', ISO639: 'eo', ISO3166: 'eu' },
|
||||||
|
estonian: { IETF: 'et-EE', ISO639: 'et', ISO3166: 'ee' },
|
||||||
|
ewe: { IETF: 'ee-GH', ISO639: 'ee', ISO3166: 'gh' },
|
||||||
|
fanagalo: { IETF: 'fn-FNG', ISO639: 'fn', ISO3166: 'za' },
|
||||||
|
faroese: { IETF: 'fo-FO', ISO639: 'fo', ISO3166: 'fo' },
|
||||||
|
fijian: { IETF: 'fj-FJ', ISO639: 'fj', ISO3166: 'fj' },
|
||||||
|
finnish: { IETF: 'fi-FI', ISO639: 'fi', ISO3166: 'fi' },
|
||||||
|
flemish: { IETF: 'nl-BE', ISO639: 'nl', ISO3166: 'be' },
|
||||||
|
fon: { IETF: 'fon-BJ', ISO639: 'fon', ISO3166: 'bj' },
|
||||||
|
french: { IETF: 'fr-FR', ISO639: 'fr', ISO3166: 'fr' },
|
||||||
|
'french canada': { IETF: 'fr-CA', ISO639: 'fr', ISO3166: 'ca' },
|
||||||
|
'french swiss': { IETF: 'fr-CH', ISO639: 'fr', ISO3166: 'ch' },
|
||||||
|
friulian: { IETF: 'fur-IT', ISO639: 'fur', ISO3166: 'it' },
|
||||||
|
fula: { IETF: 'ff-FUL', ISO639: 'ff', ISO3166: 'cm' },
|
||||||
|
galician: { IETF: 'gl-ES', ISO639: 'gl', ISO3166: 'es-ga' },
|
||||||
|
gamargu: { IETF: 'mfi-NG', ISO639: 'mfi', ISO3166: 'ng' },
|
||||||
|
garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' },
|
||||||
|
georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' },
|
||||||
|
german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' },
|
||||||
|
'Low German': { IETF: 'nl-NL', ISO639: 'nds', ISO3166: 'nl' },
|
||||||
|
gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' },
|
||||||
|
glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' },
|
||||||
|
greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' },
|
||||||
|
'grenadian creole english': { IETF: 'gcl-GD', ISO639: 'gcl', ISO3166: 'gd' },
|
||||||
|
guarani: { IETF: 'gn-PY', ISO639: 'gn', ISO3166: 'py' },
|
||||||
|
gujarati: { IETF: 'gu-IN', ISO639: 'gu', ISO3166: 'in' },
|
||||||
|
'guyanese creole english': { IETF: 'gyn-GY', ISO639: 'gyn', ISO3166: 'gy' },
|
||||||
|
'haitian creole french': { IETF: 'ht-HT', ISO639: 'ht', ISO3166: 'ht' },
|
||||||
|
'halh mongolian': { IETF: 'khk-MN', ISO639: 'khk', ISO3166: 'mn' },
|
||||||
|
hausa: { IETF: 'ha-NE', ISO639: 'ha', ISO3166: 'ne' },
|
||||||
|
hawaiian: { IETF: 'haw-US', ISO639: 'haw', ISO3166: 'xx' },
|
||||||
|
hebrew: { IETF: 'he-IL', ISO639: 'he', ISO3166: 'il' },
|
||||||
|
higi: { IETF: 'hig-NG', ISO639: 'hig', ISO3166: 'ng' },
|
||||||
|
hiligaynon: { IETF: 'hil-PH', ISO639: 'hil', ISO3166: 'ph' },
|
||||||
|
'hill mari': { IETF: 'mrj-RU', ISO639: 'mrj', ISO3166: 'xx' },
|
||||||
|
hmong: { IETF: 'hmn-CN', ISO639: 'hmn', ISO3166: 'cn' },
|
||||||
|
hungarian: { IETF: 'hu-HU', ISO639: 'hu', ISO3166: 'hu' },
|
||||||
|
icelandic: { IETF: 'is-IS', ISO639: 'is', ISO3166: 'is' },
|
||||||
|
'igbo ibo': { IETF: 'ibo-NG', ISO639: 'ibo', ISO3166: 'ng' },
|
||||||
|
'igbo ig': { IETF: 'ig-NG', ISO639: 'ig', ISO3166: 'ng' },
|
||||||
|
ilocano: { IETF: 'ilo-PH', ISO639: 'ilo', ISO3166: 'ph' },
|
||||||
|
'inuktitut greenlandic': { IETF: 'kl-GL', ISO639: 'kl', ISO3166: 'gl' },
|
||||||
|
'irish gaelic': { IETF: 'ga-IE', ISO639: 'ga', ISO3166: 'ie' },
|
||||||
|
italian: { IETF: 'it-IT', ISO639: 'it', ISO3166: 'it' },
|
||||||
|
'italian swiss': { IETF: 'it-CH', ISO639: 'it', ISO3166: 'ch' },
|
||||||
|
'jamaican creole english': { IETF: 'jam-JM', ISO639: 'jam', ISO3166: 'jm' },
|
||||||
|
japanese: { IETF: 'ja-JP', ISO639: 'ja', ISO3166: 'jp' },
|
||||||
|
javanese: { IETF: 'jv-ID', ISO639: 'jv', ISO3166: 'id' },
|
||||||
|
jingpho: { IETF: 'kac-MM', ISO639: 'kac', ISO3166: 'mm' },
|
||||||
|
"k'iche'": { IETF: 'quc-GT', ISO639: 'quc', ISO3166: 'gt' },
|
||||||
|
kabiye: { IETF: 'kbp-TG', ISO639: 'kbp', ISO3166: 'tg' },
|
||||||
|
kabuverdianu: { IETF: 'kea-CV', ISO639: 'kea', ISO3166: 'cv' },
|
||||||
|
kabylian: { IETF: 'kab-DZ', ISO639: 'kab', ISO3166: 'dz' },
|
||||||
|
kalenjin: { IETF: 'kln-KE', ISO639: 'kln', ISO3166: 'ke' },
|
||||||
|
kamba: { IETF: 'kam-KE', ISO639: 'kam', ISO3166: 'ke' },
|
||||||
|
kannada: { IETF: 'kn-IN', ISO639: 'kn', ISO3166: 'in' },
|
||||||
|
kanuri: { IETF: 'kr-KAU', ISO639: 'kr', ISO3166: 'xx' },
|
||||||
|
karen: { IETF: 'kar-MM', ISO639: 'kar', ISO3166: 'mm' },
|
||||||
|
'kashmiri (devanagari script)': { IETF: 'ks-IN', ISO639: 'ks', ISO3166: 'in' },
|
||||||
|
'kashmiri (arabic script)': { IETF: 'kas-IN', ISO639: 'kas', ISO3166: 'in' },
|
||||||
|
kazakh: { IETF: 'kk-KZ', ISO639: 'kk', ISO3166: 'kz' },
|
||||||
|
khasi: { IETF: 'kha-IN', ISO639: 'kha', ISO3166: 'in' },
|
||||||
|
khmer: { IETF: 'km-KH', ISO639: 'km', ISO3166: 'kh' },
|
||||||
|
'kikuyu kik': { IETF: 'kik-KE', ISO639: 'kik', ISO3166: 'ke' },
|
||||||
|
'kikuyu ki': { IETF: 'ki-KE', ISO639: 'ki', ISO3166: 'ke' },
|
||||||
|
kimbundu: { IETF: 'kmb-AO', ISO639: 'kmb', ISO3166: 'ao' },
|
||||||
|
kinyarwanda: { IETF: 'rw-RW', ISO639: 'rw', ISO3166: 'rw' },
|
||||||
|
kirundi: { IETF: 'rn-BI', ISO639: 'rn', ISO3166: 'bi' },
|
||||||
|
kisii: { IETF: 'guz-KE', ISO639: 'guz', ISO3166: 'ke' },
|
||||||
|
kongo: { IETF: 'kg-CG', ISO639: 'kg', ISO3166: 'cg' },
|
||||||
|
konkani: { IETF: 'kok-IN', ISO639: 'kok', ISO3166: 'in' },
|
||||||
|
korean: { IETF: 'ko-KR', ISO639: 'ko', ISO3166: 'kr' },
|
||||||
|
'northern kurdish': { IETF: 'kmr-TR', ISO639: 'kmr', ISO3166: 'tr' },
|
||||||
|
'kurdish sorani': { IETF: 'ckb-IQ', ISO639: 'ckb', ISO3166: 'iq' },
|
||||||
|
kyrgyz: { IETF: 'ky-KG', ISO639: 'ky', ISO3166: 'kg' },
|
||||||
|
lao: { IETF: 'lo-LA', ISO639: 'lo', ISO3166: 'la' },
|
||||||
|
latgalian: { IETF: 'ltg-LV', ISO639: 'ltg', ISO3166: 'lv' },
|
||||||
|
latin: { IETF: 'la-XN', ISO639: 'la', ISO3166: 'xx' },
|
||||||
|
latvian: { IETF: 'lv-LV', ISO639: 'lv', ISO3166: 'lg' },
|
||||||
|
ligurian: { IETF: 'lij-IT', ISO639: 'lij', ISO3166: 'it' },
|
||||||
|
limburgish: { IETF: 'li-NL', ISO639: 'li', ISO3166: 'nl' },
|
||||||
|
lingala: { IETF: 'ln-LIN', ISO639: 'ln', ISO3166: 'cd' },
|
||||||
|
lithuanian: { IETF: 'lt-LT', ISO639: 'lt', ISO3166: 'lt' },
|
||||||
|
lombard: { IETF: 'lmo-IT', ISO639: 'lmo', ISO3166: 'it' },
|
||||||
|
'luba-kasai': { IETF: 'lua-CD', ISO639: 'lua', ISO3166: 'cd' },
|
||||||
|
luganda: { IETF: 'lg-UG', ISO639: 'lg', ISO3166: 'ug' },
|
||||||
|
luhya: { IETF: 'luy-KE', ISO639: 'luy', ISO3166: 'ke' },
|
||||||
|
luo: { IETF: 'luo-KE', ISO639: 'luo', ISO3166: 'ke' },
|
||||||
|
luxembourgish: { IETF: 'lb-LU', ISO639: 'lb', ISO3166: 'lu' },
|
||||||
|
maa: { IETF: 'mas-KE', ISO639: 'mas', ISO3166: 'ke' },
|
||||||
|
macedonian: { IETF: 'mk-MK', ISO639: 'mk', ISO3166: 'mk' },
|
||||||
|
magahi: { IETF: 'mag-IN', ISO639: 'mag', ISO3166: 'in' },
|
||||||
|
maithili: { IETF: 'mai-IN', ISO639: 'mai', ISO3166: 'in' },
|
||||||
|
malagasy: { IETF: 'mg-MG', ISO639: 'mg', ISO3166: 'mg' },
|
||||||
|
malay: { IETF: 'ms-MY', ISO639: 'ms', ISO3166: 'my' },
|
||||||
|
malayalam: { IETF: 'ml-IN', ISO639: 'ml', ISO3166: 'in' },
|
||||||
|
maldivian: { IETF: 'dv-MV', ISO639: 'dv', ISO3166: 'mv' },
|
||||||
|
maltese: { IETF: 'mt-MT', ISO639: 'mt', ISO3166: 'mt' },
|
||||||
|
mandara: { IETF: 'mfi-CM', ISO639: 'mfi', ISO3166: 'cm' },
|
||||||
|
manipuri: { IETF: 'mni-IN', ISO639: 'mni', ISO3166: 'in' },
|
||||||
|
'manx gaelic': { IETF: 'gv-IM', ISO639: 'gv', ISO3166: 'im' },
|
||||||
|
maori: { IETF: 'mi-NZ', ISO639: 'mi', ISO3166: 'nz' },
|
||||||
|
marathi: { IETF: 'mr-IN', ISO639: 'mr', ISO3166: 'in' },
|
||||||
|
margi: { IETF: 'mrt-NG', ISO639: 'mrt', ISO3166: 'ng' },
|
||||||
|
mari: { IETF: 'mhr-RU', ISO639: 'mhr', ISO3166: 'xx' },
|
||||||
|
marshallese: { IETF: 'mh-MH', ISO639: 'mh', ISO3166: 'mh' },
|
||||||
|
mende: { IETF: 'men-SL', ISO639: 'men', ISO3166: 'sl' },
|
||||||
|
meru: { IETF: 'mer-KE', ISO639: 'mer', ISO3166: 'ke' },
|
||||||
|
mijikenda: { IETF: 'nyf-KE', ISO639: 'nyf', ISO3166: 'ke' },
|
||||||
|
minangkabau: { IETF: 'min-ID', ISO639: 'min', ISO3166: 'id' },
|
||||||
|
mizo: { IETF: 'lus-IN', ISO639: 'lus', ISO3166: 'in' },
|
||||||
|
mongolian: { IETF: 'mn-MN', ISO639: 'mn', ISO3166: 'mn' },
|
||||||
|
montenegrin: { IETF: 'sr-ME', ISO639: 'sr', ISO3166: 'me' },
|
||||||
|
morisyen: { IETF: 'mfe-MU', ISO639: 'mfe', ISO3166: 'mu' },
|
||||||
|
'moroccan arabic': { IETF: 'ar-MA', ISO639: 'ar', ISO3166: 'ma' },
|
||||||
|
mossi: { IETF: 'mos-BF', ISO639: 'mos', ISO3166: 'bf' },
|
||||||
|
ndau: { IETF: 'ndc-MZ', ISO639: 'ndc', ISO3166: 'mz' },
|
||||||
|
ndebele: { IETF: 'nr-ZA', ISO639: 'nr', ISO3166: 'za' },
|
||||||
|
nepali: { IETF: 'ne-NP', ISO639: 'ne', ISO3166: 'np' },
|
||||||
|
'nigerian fulfulde': { IETF: 'fuv-NG', ISO639: 'fuv', ISO3166: 'ng' },
|
||||||
|
niuean: { IETF: 'niu-NU', ISO639: 'niu', ISO3166: 'nu' },
|
||||||
|
'north azerbaijani': { IETF: 'azj-AZ', ISO639: 'azj', ISO3166: 'az' },
|
||||||
|
sesotho: { IETF: 'nso-ZA', ISO639: 'nso', ISO3166: 'za' },
|
||||||
|
'northern uzbek': { IETF: 'uzn-UZ', ISO639: 'uzn', ISO3166: 'uz' },
|
||||||
|
'norwegian bokm<6B>l': { IETF: 'nb-NO', ISO639: 'nb', ISO3166: 'no' },
|
||||||
|
'norwegian nynorsk': { IETF: 'nn-NO', ISO639: 'nn', ISO3166: 'no' },
|
||||||
|
nuer: { IETF: 'nus-SS', ISO639: 'nus', ISO3166: 'ss' },
|
||||||
|
nyanja: { IETF: 'ny-MW', ISO639: 'ny', ISO3166: 'mw' },
|
||||||
|
occitan: { IETF: 'oc-FR', ISO639: 'oc', ISO3166: 'fr' },
|
||||||
|
'occitan aran': { IETF: 'oc-ES', ISO639: 'oc', ISO3166: 'es-ct' },
|
||||||
|
odia: { IETF: 'or-IN', ISO639: 'or', ISO3166: 'in' },
|
||||||
|
oriya: { IETF: 'ory-IN', ISO639: 'ory', ISO3166: 'in' },
|
||||||
|
urdu: { IETF: 'ur-PK', ISO639: 'ur', ISO3166: 'pk' },
|
||||||
|
palauan: { IETF: 'pau-PW', ISO639: 'pau', ISO3166: 'pw' },
|
||||||
|
pali: { IETF: 'pi-IN', ISO639: 'pi', ISO3166: 'in' },
|
||||||
|
pangasinan: { IETF: 'pag-PH', ISO639: 'pag', ISO3166: 'ph' },
|
||||||
|
papiamentu: { IETF: 'pap-CW', ISO639: 'pap', ISO3166: 'cw' },
|
||||||
|
pashto: { IETF: 'ps-PK', ISO639: 'ps', ISO3166: 'pk' },
|
||||||
|
persian: { IETF: 'fa-IR', ISO639: 'fa', ISO3166: 'ir' },
|
||||||
|
pijin: { IETF: 'pis-SB', ISO639: 'pis', ISO3166: 'sb' },
|
||||||
|
'plateau malagasy': { IETF: 'plt-MG', ISO639: 'plt', ISO3166: 'mg' },
|
||||||
|
polish: { IETF: 'pl-PL', ISO639: 'pl', ISO3166: 'pl' },
|
||||||
|
portuguese: { IETF: 'pt-PT', ISO639: 'pt', ISO3166: 'pt' },
|
||||||
|
'portuguese brazil': { IETF: 'pt-BR', ISO639: 'pt', ISO3166: 'br' },
|
||||||
|
potawatomi: { IETF: 'pot-US', ISO639: 'pot', ISO3166: 'us' },
|
||||||
|
punjabi: { IETF: 'pa-IN', ISO639: 'pa', ISO3166: 'in' },
|
||||||
|
'punjabi (pakistan)': { IETF: 'pnb-PK', ISO639: 'pnb', ISO3166: 'pk' },
|
||||||
|
quechua: { IETF: 'qu-PE', ISO639: 'qu', ISO3166: 'pe' },
|
||||||
|
rohingya: { IETF: 'rhg-MM', ISO639: 'rhg', ISO3166: 'mm' },
|
||||||
|
rohingyalish: { IETF: 'rhl-MM', ISO639: 'rhl', ISO3166: 'mm' },
|
||||||
|
romanian: { IETF: 'ro-RO', ISO639: 'ro', ISO3166: 'ro' },
|
||||||
|
romansh: { IETF: 'roh-CH', ISO639: 'roh', ISO3166: 'ch' },
|
||||||
|
rundi: { IETF: 'run-BI', ISO639: 'run', ISO3166: 'bi' },
|
||||||
|
'saint lucian creole french': { IETF: 'acf-LC', ISO639: 'acf', ISO3166: 'lc' },
|
||||||
|
samoan: { IETF: 'sm-WS', ISO639: 'sm', ISO3166: 'ws' },
|
||||||
|
sango: { IETF: 'sg-CF', ISO639: 'sg', ISO3166: 'cf' },
|
||||||
|
sanskrit: { IETF: 'sa-IN', ISO639: 'sa', ISO3166: 'in' },
|
||||||
|
santali: { IETF: 'sat-IN', ISO639: 'sat', ISO3166: 'in' },
|
||||||
|
sardinian: { IETF: 'sc-IT', ISO639: 'sc', ISO3166: 'it' },
|
||||||
|
'scots gaelic': { IETF: 'gd-GB', ISO639: 'gd', ISO3166: 'gb-sct' },
|
||||||
|
sena: { IETF: 'seh-ZW', ISO639: 'seh', ISO3166: 'zw' },
|
||||||
|
'serbian cyrillic': { IETF: 'sr-Cyrl-RS', ISO639: 'sr', ISO3166: 'rs' },
|
||||||
|
'serbian latin': { IETF: 'sr-Latn-RS', ISO639: 'sr', ISO3166: 'rs' },
|
||||||
|
'seselwa creole french': { IETF: 'crs-SC', ISO639: 'crs', ISO3166: 'sc' },
|
||||||
|
'setswana (south africa)': { IETF: 'tn-ZA', ISO639: 'tn', ISO3166: 'za' },
|
||||||
|
shan: { IETF: 'shn-MM', ISO639: 'shn', ISO3166: 'mm' },
|
||||||
|
shona: { IETF: 'sn-ZW', ISO639: 'sn', ISO3166: 'zw' },
|
||||||
|
sicilian: { IETF: 'scn-IT', ISO639: 'scn', ISO3166: 'it' },
|
||||||
|
silesian: { IETF: 'szl-PL', ISO639: 'szl', ISO3166: 'pl' },
|
||||||
|
'sindhi snd': { IETF: 'snd-PK', ISO639: 'snd', ISO3166: 'pk' },
|
||||||
|
'sindhi sd': { IETF: 'sd-PK', ISO639: 'sd', ISO3166: 'pk' },
|
||||||
|
sinhala: { IETF: 'si-LK', ISO639: 'si', ISO3166: 'lk' },
|
||||||
|
slovak: { IETF: 'sk-SK', ISO639: 'sk', ISO3166: 'sk' },
|
||||||
|
slovenian: { IETF: 'sl-SI', ISO639: 'sl', ISO3166: 'si' },
|
||||||
|
somali: { IETF: 'so-SO', ISO639: 'so', ISO3166: 'so' },
|
||||||
|
'sotho southern': { IETF: 'st-LS', ISO639: 'st', ISO3166: 'ls' },
|
||||||
|
'south azerbaijani': { IETF: 'azb-AZ', ISO639: 'azb', ISO3166: 'az' },
|
||||||
|
'southern pashto': { IETF: 'pbt-PK', ISO639: 'pbt', ISO3166: 'pk' },
|
||||||
|
'southwestern dinka': { IETF: 'dik-SS', ISO639: 'dik', ISO3166: 'ss' },
|
||||||
|
'spanish argentina': { IETF: 'es-AR', ISO639: 'es', ISO3166: 'ar' },
|
||||||
|
'spanish colombia': { IETF: 'es-CO', ISO639: 'es', ISO3166: 'co' },
|
||||||
|
'spanish latin america': { IETF: 'es-419', ISO639: 'es', ISO3166: 'do' },
|
||||||
|
'spanish mexico': { IETF: 'es-MX', ISO639: 'es', ISO3166: 'mx' },
|
||||||
|
'spanish united states': { IETF: 'es-US', ISO639: 'es', ISO3166: 'es' },
|
||||||
|
'sranan tongo': { IETF: 'srn-SR', ISO639: 'srn', ISO3166: 'sr' },
|
||||||
|
'standard latvian': { IETF: 'lvs-LV', ISO639: 'lvs', ISO3166: 'lv' },
|
||||||
|
'standard malay': { IETF: 'zsm-MY', ISO639: 'zsm', ISO3166: 'my' },
|
||||||
|
sundanese: { IETF: 'su-ID', ISO639: 'su', ISO3166: 'id' },
|
||||||
|
swahili: { IETF: 'sw-KE', ISO639: 'sw', ISO3166: 'ke' },
|
||||||
|
swati: { IETF: 'ss-SZ', ISO639: 'ss', ISO3166: 'sz' },
|
||||||
|
swedish: { IETF: 'sv-SE', ISO639: 'sv', ISO3166: 'se' },
|
||||||
|
'swiss german': { IETF: 'de-CH', ISO639: 'de', ISO3166: 'ch' },
|
||||||
|
'syriac (aramaic)': { IETF: 'syc-TR', ISO639: 'syc', ISO3166: 'tr' },
|
||||||
|
tagalog: { IETF: 'tl-PH', ISO639: 'tl', ISO3166: 'ph' },
|
||||||
|
tahitian: { IETF: 'ty-PF', ISO639: 'ty', ISO3166: 'pf' },
|
||||||
|
tajik: { IETF: 'tg-TJ', ISO639: 'tg', ISO3166: 'tj' },
|
||||||
|
'tamashek (tuareg)': { IETF: 'tmh-DZ', ISO639: 'tmh', ISO3166: 'dz' },
|
||||||
|
tamasheq: { IETF: 'taq-ML', ISO639: 'taq', ISO3166: 'ml' },
|
||||||
|
'tamil india': { IETF: 'ta-IN', ISO639: 'ta', ISO3166: 'in' },
|
||||||
|
'tamil sri lanka': { IETF: 'ta-LK', ISO639: 'ta', ISO3166: 'lk' },
|
||||||
|
taroko: { IETF: 'trv-TW', ISO639: 'trv', ISO3166: 'tw' },
|
||||||
|
tatar: { IETF: 'tt-RU', ISO639: 'tt', ISO3166: 'ru' },
|
||||||
|
telugu: { IETF: 'te-IN', ISO639: 'te', ISO3166: 'in' },
|
||||||
|
tetum: { IETF: 'tet-TL', ISO639: 'tet', ISO3166: 'tl' },
|
||||||
|
thai: { IETF: 'th-TH', ISO639: 'th', ISO3166: 'th' },
|
||||||
|
tibetan: { IETF: 'bo-CN', ISO639: 'bo', ISO3166: 'cn' },
|
||||||
|
tigrinya: { IETF: 'ti-ET', ISO639: 'ti', ISO3166: 'et' },
|
||||||
|
'tok pisin': { IETF: 'tpi-PG', ISO639: 'tpi', ISO3166: 'pg' },
|
||||||
|
tokelauan: { IETF: 'tkl-TK', ISO639: 'tkl', ISO3166: 'tk' },
|
||||||
|
tongan: { IETF: 'to-TO', ISO639: 'to', ISO3166: 'to' },
|
||||||
|
'tosk albanian': { IETF: 'als-AL', ISO639: 'als', ISO3166: 'al' },
|
||||||
|
tsonga: { IETF: 'ts-ZA', ISO639: 'ts', ISO3166: 'za' },
|
||||||
|
tswa: { IETF: 'tsc-MZ', ISO639: 'tsc', ISO3166: 'mz' },
|
||||||
|
tswana: { IETF: 'tn-BW', ISO639: 'tn', ISO3166: 'bw' },
|
||||||
|
tumbuka: { IETF: 'tum-MW', ISO639: 'tum', ISO3166: 'mw' },
|
||||||
|
turkmen: { IETF: 'tk-TM', ISO639: 'tk', ISO3166: 'tm' },
|
||||||
|
tuvaluan: { IETF: 'tvl-TV', ISO639: 'tvl', ISO3166: 'tv' },
|
||||||
|
twi: { IETF: 'tw-GH', ISO639: 'tw', ISO3166: 'gh' },
|
||||||
|
udmurt: { IETF: 'udm-RU', ISO639: 'udm', ISO3166: 'xx' },
|
||||||
|
ukrainian: { IETF: 'uk-UA', ISO639: 'uk', ISO3166: 'ua' },
|
||||||
|
uma: { IETF: 'ppk-ID', ISO639: 'ppk', ISO3166: 'id' },
|
||||||
|
umbundu: { IETF: 'umb-AO', ISO639: 'umb', ISO3166: 'ao' },
|
||||||
|
'uyghur uig': { IETF: 'uig-CN', ISO639: 'uig', ISO3166: 'cn' },
|
||||||
|
'uyghur ug': { IETF: 'ug-CN', ISO639: 'ug', ISO3166: 'cn' },
|
||||||
|
uzbek: { IETF: 'uz-UZ', ISO639: 'uz', ISO3166: 'uz' },
|
||||||
|
venetian: { IETF: 'vec-IT', ISO639: 'vec', ISO3166: 'it' },
|
||||||
|
vietnamese: { IETF: 'vi-VN', ISO639: 'vi', ISO3166: 'vn' },
|
||||||
|
'vincentian creole english': { IETF: 'svc-VC', ISO639: 'svc', ISO3166: 'vc' },
|
||||||
|
'virgin islands creole english': { IETF: 'vic-US', ISO639: 'vic', ISO3166: 'vi' },
|
||||||
|
wallisian: { IETF: 'wls-WF', ISO639: 'wls', ISO3166: 'wf' },
|
||||||
|
'waray (philippines)': { IETF: 'war-PH', ISO639: 'war', ISO3166: 'ph' },
|
||||||
|
welsh: { IETF: 'cy-GB', ISO639: 'cy', ISO3166: 'gb-wls' },
|
||||||
|
'west central oromo': { IETF: 'gaz-ET', ISO639: 'gaz', ISO3166: 'et' },
|
||||||
|
'western persian': { IETF: 'pes-IR', ISO639: 'pes', ISO3166: 'ir' },
|
||||||
|
wolof: { IETF: 'wo-SN', ISO639: 'wo', ISO3166: 'sn' },
|
||||||
|
xhosa: { IETF: 'xh-ZA', ISO639: 'xh', ISO3166: 'za' },
|
||||||
|
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 };
|
||||||
105
src/js/logger.js
|
|
@ -6,76 +6,75 @@ 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
|
format.printf(info => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`)
|
||||||
} ${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 fetching log file:', error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = logger;
|
module.exports = logger;
|
||||||
|
|
|
||||||
51
src/js/mediaDevices.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* global settings, */
|
||||||
|
|
||||||
|
const micSelect = document.querySelector('#microphone');
|
||||||
|
let selectedMic;
|
||||||
|
|
||||||
|
function getAvailableMediaDevices(type) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
navigator.mediaDevices
|
||||||
|
.enumerateDevices()
|
||||||
|
.then(devices => {
|
||||||
|
const microphones = devices.filter(device => device.kind === type);
|
||||||
|
resolve(microphones);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Microphones
|
||||||
|
getAvailableMediaDevices('audioinput')
|
||||||
|
.then(microphones => {
|
||||||
|
let i = 0;
|
||||||
|
let tempname = '';
|
||||||
|
for (const mic of microphones) {
|
||||||
|
if (mic.deviceId === 'default') {
|
||||||
|
tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mic.deviceId === 'communications' || mic.label === tempname) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = document.createElement('option');
|
||||||
|
|
||||||
|
// Set the options value and text.
|
||||||
|
option.value = i;
|
||||||
|
option.innerHTML = `${mic.label}`;
|
||||||
|
|
||||||
|
// Add the option to the voice selector.
|
||||||
|
micSelect.appendChild(option);
|
||||||
|
|
||||||
|
if (i === microphones.length - 1) {
|
||||||
|
document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error retrieving microphones:', error);
|
||||||
|
});
|
||||||
|
|
@ -1,61 +1,51 @@
|
||||||
const twitchTemplate = `
|
const twitchTemplate = `
|
||||||
<div class="mmg">
|
<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>
|
||||||
<div class="msg-box">
|
`.trim();
|
||||||
<div class="flr">
|
|
||||||
<div class="messages">
|
const trovoTemplate = `
|
||||||
<span class="timestamp">
|
<img class="user-img" src="" />
|
||||||
<span class="username"></span>
|
<img class="status-circle sender" src="./images/trovo-icon.jpg" tip="Trovo" />
|
||||||
<span class="post-time"></span>
|
<span class="post-time sender"></span>
|
||||||
</span>
|
<span class="username sender"></span>
|
||||||
<br>
|
<div class="msg-box sender"></div>
|
||||||
<p class="msg"></p>
|
`.trim();
|
||||||
</div>
|
|
||||||
</div>
|
const youtubeTemplate = `
|
||||||
</div>
|
<img class="user-img" src="" />
|
||||||
</div>
|
<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 = `
|
||||||
<div class="icon-container-user">
|
<img class="user-img" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
||||||
<img class="user-img-user" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
<img class="status-circle user" src="./images/twitch-icon.png" />
|
||||||
<img class="status-circle-user" src="./images/twitch-icon.png" />
|
<span class="post-time user"></span>
|
||||||
</div>
|
<span class="username user">You</span>
|
||||||
<div class="msg-box-user">
|
<div class="msg-box user"></div>
|
||||||
<div class="flr">
|
|
||||||
<div class="messages-user">
|
|
||||||
<span class="timestamp">
|
|
||||||
<span class="username">You</span>
|
|
||||||
<span class="post-time"></span>
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<p class="msg"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
const messageTemplate = `
|
const messageTemplate = `
|
||||||
<article class="msg-container msg-self" id="msg-0">
|
<article class=" user">
|
||||||
<div class="icon-container-user">
|
<img class="user-img" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
||||||
<img class="user-img-user" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
<img class="status-circle user" src="./images/twitch-icon.png" />
|
||||||
<img class="status-circle-user" src="./images/twitch-icon.png" />
|
<span class="post-time user"> 12:00 PM</span>
|
||||||
</div>
|
<span class="username user">You</span>
|
||||||
<div class="msg-box-user msg-box-user-temp">
|
<div class="msg-box user">Hello there</div>
|
||||||
<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, userTemplate, messageTemplate };
|
module.exports = { twitchTemplate, dliveTemplate, youtubeTemplate, userTemplate, messageTemplate, trovoTemplate };
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,30 @@
|
||||||
const path = require('path'); // get directory path
|
/* eslint-disable no-unused-vars */
|
||||||
const {
|
|
||||||
ipcRenderer,
|
|
||||||
} = require('electron'); // necessary electron libraries to send data to the app
|
|
||||||
const say = require('say');
|
|
||||||
const request = require('request');
|
|
||||||
const langdetect = require('langdetect');
|
|
||||||
const io = require('socket.io-client');
|
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const axios = require('axios');
|
||||||
|
const Sockette = require('sockette');
|
||||||
|
|
||||||
const util = require('util');
|
const { webFrame, ipcRenderer, shell } = require('electron');
|
||||||
const exec = util.promisify(require('child_process').exec);
|
const io = require('socket.io-client');
|
||||||
|
|
||||||
const GoogleTTS = require('node-google-tts-api');
|
const GoogleTTS = require('node-google-tts-api');
|
||||||
|
|
||||||
const tts = new GoogleTTS();
|
const tts = new GoogleTTS();
|
||||||
const { Socket } = require('socket.io-client');
|
const { Socket } = require('socket.io-client');
|
||||||
|
|
||||||
const ini = require('ini');
|
const main = ipcRenderer.sendSync('environment');
|
||||||
|
|
||||||
let envInfo = (ipcRenderer.sendSync('environment'))
|
const resourcesPath = main.resourcesPath;
|
||||||
|
const settingsPath = main.settingsPath.toString();
|
||||||
|
const pythonPath = main.pythonPath.toString();
|
||||||
|
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 =
|
||||||
const languagesObject = fs.readFileSync(path.join(__dirname, './config/languages.txt')).toString().split('\r\n');
|
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,300 +33,547 @@ const amazonVoiceSelect = document.querySelector('#amazonVoice'); // obtain the
|
||||||
const notificationAudioDevices = document.querySelector('#notificationAudioDevice'); // obtain the html reference of the installedTTS comboBox
|
const notificationAudioDevices = document.querySelector('#notificationAudioDevice'); // obtain the html reference of the installedTTS comboBox
|
||||||
const devicesDropdown = document.querySelector('#devicesDropdown');
|
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 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'));
|
||||||
|
|
||||||
const messageTemplates = require(path.join(__dirname, './js/messageTemplates'));
|
const messageTemplates = require(path.join(__dirname, './js/messageTemplates'));
|
||||||
|
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 talk = require(path.join(__dirname, './js/voiceQueue')); // Voice queue system
|
|
||||||
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'));
|
||||||
|
|
||||||
let notificationSounds = undefined;
|
const betterTTVAutocomplete = new TokenAutocomplete({
|
||||||
if (envInfo.env) {
|
name: 'sample',
|
||||||
notificationSounds = path.join(envInfo.path, './sounds/notifications');
|
selector: '#sample',
|
||||||
} else {
|
noMatchesText: 'No matching results...',
|
||||||
notificationSounds = path.join(__dirname, './sounds/notifications');
|
initialTokens: [...settings.TWITCH.BETTERTTV_CHANNELS]
|
||||||
|
});
|
||||||
|
|
||||||
|
const test = settings.TWITCH.BETTERTTV_CHANNELS;
|
||||||
|
console.log(test);
|
||||||
|
|
||||||
|
const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
|
||||||
|
|
||||||
|
const notificationSounds = path.join(resourcesPath, main.isPackaged ? './sounds/notifications' : '../sounds/notifications');
|
||||||
|
const sttModels = path.join(resourcesPath, main.isPackaged ? './speech_to_text_models' : '../speech_to_text_models');
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
ipcRenderer.send('restart');
|
||||||
}
|
}
|
||||||
|
|
||||||
const twitch = config.settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
|
const server = require(path.join(__dirname, './js/server'));
|
||||||
|
const backend = require(path.join(__dirname, './js/backend'));
|
||||||
|
const socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server
|
||||||
|
|
||||||
let server;
|
let twitch = null;
|
||||||
let socket;
|
twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
|
||||||
|
|
||||||
if (config.settings.SERVER.USE_SERVER) {
|
let dlive = null;
|
||||||
server = require(path.join(__dirname, './js/server'));
|
dlive = settings.DLIVE.USE_DLIVE ? require(path.join(__dirname, './js/dlive')) : '';
|
||||||
socket = io(`http://localhost:${config.settings.SERVER.PORT}`); // Connect to your Socket.IO server
|
|
||||||
}
|
|
||||||
|
|
||||||
const Polly = config.settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : '';
|
let youtube = null;
|
||||||
const google = config.settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/amazon')) : '';
|
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')) : '';
|
||||||
|
|
||||||
|
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 theme = require(path.join(__dirname, './js/theme'));
|
const theme = require(path.join(__dirname, './js/theme'));
|
||||||
|
const auth = require(path.join(__dirname, './js/auth'));
|
||||||
|
|
||||||
|
let ttsRequestCount = 0;
|
||||||
|
ttsRequestCount = 0;
|
||||||
|
let customEmojis = [];
|
||||||
|
customEmojis = [];
|
||||||
|
let messageId = 0;
|
||||||
|
messageId = 0;
|
||||||
|
let customEmojiList = [];
|
||||||
|
|
||||||
// initialize values
|
// initialize values
|
||||||
config.getGeneralSettings();
|
config.getGeneralSettings();
|
||||||
config.setCustomThemeToggle();
|
|
||||||
|
|
||||||
|
|
||||||
let selectedVoiceIndex;
|
|
||||||
let selectedEncodingIndex;
|
|
||||||
let selectedTtsAudioDeviceIndex;
|
|
||||||
const TTSVolume = 1;
|
const TTSVolume = 1;
|
||||||
|
|
||||||
const notificationSoundVolume = 1;
|
const notificationSoundVolume = 1;
|
||||||
// const slider = document.body.querySelector('#slider');
|
|
||||||
const StartDateAndTime = Date.now();
|
const StartDateAndTime = Date.now();
|
||||||
const speakButton = document.querySelector('#speakBtn');
|
const speakButton = document.querySelector('#speakBtn');
|
||||||
|
|
||||||
const amazonCredentials = {
|
const amazonCredentials = {
|
||||||
accessKeyId: config.settings.AMAZON.ACCESS_KEY,
|
accessKeyId: settings.AMAZON.ACCESS_KEY,
|
||||||
secretAccessKey: config.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) => {
|
||||||
files.forEach((file, i) => {
|
if (err) {
|
||||||
// Create a new option element.
|
console.error(err);
|
||||||
const option = document.createElement('option');
|
}
|
||||||
|
|
||||||
// Set the options value and text.
|
files.forEach((file, i) => {
|
||||||
option.value = i;
|
// Create a new option element.
|
||||||
option.innerHTML = file;
|
const option = document.createElement('option');
|
||||||
|
|
||||||
// Add the option to the sound selector.
|
// Set the options value and text.
|
||||||
notificationSound.appendChild(option);
|
option.value = i;
|
||||||
});
|
option.innerHTML = file;
|
||||||
|
|
||||||
// set the saved notification sound
|
// Add the option to the sound selector.
|
||||||
notificationSound.selectedIndex = config.settings.AUDIO.NOTIFICATION_SOUND;
|
notificationSound.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// set the saved notification sound
|
||||||
|
notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check for installed stt models
|
||||||
|
fs.readdir(sttModels, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.includes('.txt')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Create a new option element.
|
||||||
|
const option = document.createElement('option');
|
||||||
|
|
||||||
|
// Set the options value and text.
|
||||||
|
option.value = file;
|
||||||
|
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) {
|
||||||
// logger.info('enumerateDevices() not supported.');
|
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 option = document.createElement('option');
|
const option1 = document.createElement('option');
|
||||||
option.text = device.label || `Output ${device.deviceId}`;
|
const option2 = document.createElement('option');
|
||||||
option.value = device.deviceId;
|
option1.text = device.label || `Output ${device.deviceId}`;
|
||||||
ttsAudioDevices.appendChild(option);
|
option2.text = device.label || `Output ${device.deviceId}`;
|
||||||
});
|
option1.value = device.deviceId;
|
||||||
|
option2.value = device.deviceId;
|
||||||
|
ttsAudioDevices.appendChild(option1);
|
||||||
|
notificationSoundAudioDevices.appendChild(option2);
|
||||||
|
});
|
||||||
|
|
||||||
ttsAudioDevices.selectedIndex = config.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) {
|
||||||
let languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
|
const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
|
||||||
|
|
||||||
const languages = Object.keys(languagesObject);
|
for (const language in languageObject.languages) {
|
||||||
languages.forEach((language) => {
|
if (Object.prototype.hasOwnProperty.call(languageObject.languages, language)) {
|
||||||
const option = document.createElement('option');
|
const IETF = languageObject.languages[language].IETF;
|
||||||
|
const ISO639 = languageObject.languages[language].ISO639;
|
||||||
|
const option = document.createElement('option');
|
||||||
|
|
||||||
option.value = language;
|
option.value = IETF;
|
||||||
option.innerHTML = languagesObject[language];
|
option.innerHTML = `${ISO639} : ${language}`;
|
||||||
|
languageSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
languageSelect.appendChild(option);
|
languageSelect.selectedIndex = setting;
|
||||||
});
|
|
||||||
|
|
||||||
languageSelect.selectedIndex = setting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguagesinSelect("#primaryLanguage", config.settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX);
|
setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE_INDEX);
|
||||||
setLanguagesinSelect("#secondaryLanguage", config.settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX);
|
setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX);
|
||||||
|
setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX);
|
||||||
|
setLanguagesinSelect('#TRANSLATE_TO', settings.LANGUAGE.TRANSLATE_TO_INDEX);
|
||||||
|
|
||||||
function getInstalledVoices(callback) {
|
function addVoiceService(name) {
|
||||||
say.getInstalledVoices((err, voices) => {
|
function addToselect(select) {
|
||||||
|
const ttsService = document.querySelector(select);
|
||||||
|
const option = document.createElement('option');
|
||||||
|
ttsService.appendChild(option);
|
||||||
|
|
||||||
function setVoicesinSelect(voiceSelector) {
|
option.value = name;
|
||||||
let voiceSelect = document.querySelector(voiceSelector); // obtain the html reference of the google voices comboBox
|
option.innerHTML = name;
|
||||||
|
}
|
||||||
const internalTTSHeader = document.createElement('optgroup');
|
addToselect('#primaryTTSService');
|
||||||
internalTTSHeader.label = "Internal TTS";
|
addToselect('#secondaryTTSService');
|
||||||
voiceSelect.appendChild(internalTTSHeader);
|
|
||||||
|
|
||||||
// const installedTTS = document.querySelector('#installedTTS'); // obtain the html reference of the installedTTS comboBox
|
|
||||||
voices.forEach((voice, i) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
|
|
||||||
option.value = i;
|
|
||||||
option.innerHTML = voice;
|
|
||||||
|
|
||||||
// installedTTS.appendChild(option);
|
|
||||||
internalTTSHeader.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setVoicesinSelect("#primaryVoice");
|
|
||||||
setVoicesinSelect("#secondaryVoice");
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAmazonVoices(callback) {
|
function determineTootlTipPosition(element) {
|
||||||
if (!config.settings.AMAZON.USE_AMAZON) {
|
const horizontal = document.body.clientWidth / 2;
|
||||||
callback();
|
const vertical = document.body.clientHeight / 2;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVoicesinSelect(voiceSelector) {
|
element.tip.style.left = `${element.mouse.x}px`;
|
||||||
let voiceSelect = document.querySelector(voiceSelector); // obtain the html reference of the google voices comboBox
|
element.tip.style.top = `${element.mouse.y}px`;
|
||||||
|
|
||||||
const internalTTSHeader = document.createElement('optgroup');
|
const tipPosition = element.tip.getBoundingClientRect();
|
||||||
internalTTSHeader.label = "Amazon TTS";
|
|
||||||
voiceSelect.appendChild(internalTTSHeader);
|
|
||||||
|
|
||||||
const voices = Object.keys(amazonVoices);
|
if (element.position.x < horizontal && element.position.y < vertical) {
|
||||||
voices.forEach((voice) => {
|
element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`;
|
||||||
const option = document.createElement('option');
|
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
|
||||||
|
}
|
||||||
|
|
||||||
option.value = voice;
|
if (element.position.x < horizontal && element.position.y > vertical) {
|
||||||
option.innerHTML = amazonVoices[voice];
|
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
|
||||||
|
}
|
||||||
|
|
||||||
internalTTSHeader.appendChild(option);
|
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`;
|
||||||
|
}
|
||||||
|
|
||||||
setVoicesinSelect("#primaryVoice");
|
if (element.position.x > horizontal && element.position.y > vertical) {
|
||||||
setVoicesinSelect("#secondaryVoice");
|
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`;
|
||||||
|
}
|
||||||
|
|
||||||
callback();
|
element.tip.style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGoogleVoices(callback) {
|
|
||||||
if (!config.settings.GOOGLE.USE_GOOGLE) {
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVoicesinSelect(voiceSelector) {
|
|
||||||
let voiceSelect = document.querySelector(voiceSelector); // obtain the html reference of the google voices comboBox
|
|
||||||
|
|
||||||
const internalTTSHeader = document.createElement('optgroup');
|
|
||||||
internalTTSHeader.label = "Google TTS";
|
|
||||||
voiceSelect.appendChild(internalTTSHeader);
|
|
||||||
|
|
||||||
const googleVoiceSelect = document.querySelector('#googleVoice'); // obtain the html reference of the google voices comboBox
|
|
||||||
const voices = Object.keys(googleVoices);
|
|
||||||
voices.forEach((voice) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.classList.add("option");
|
|
||||||
|
|
||||||
option.value = voice;
|
|
||||||
option.innerHTML = googleVoices[voice];
|
|
||||||
|
|
||||||
internalTTSHeader.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setVoicesinSelect("#primaryVoice");
|
|
||||||
setVoicesinSelect("#secondaryVoice");
|
|
||||||
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
getGoogleVoices(function () {
|
|
||||||
getAmazonVoices(function () {
|
|
||||||
getInstalledVoices(function () {
|
|
||||||
let primaryVoice = document.querySelector("#primaryVoice");
|
|
||||||
primaryVoice.selectedIndex = config.settings.TTS.PRIMARY_TTS_VOICE;
|
|
||||||
|
|
||||||
let secondaryVoice = document.querySelector("#secondaryVoice");
|
|
||||||
secondaryVoice.selectedIndex = config.settings.TTS.SECONDARY_TTS_VOICE;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Small tooltip
|
// Small tooltip
|
||||||
Array.from(document.body.querySelectorAll('[tip]')).forEach((el) => {
|
function addSingleTooltip(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.classList.add('tooltiptext');
|
tip.innerText = el.getAttribute('tip');
|
||||||
tip.innerText = el.getAttribute('tip');
|
if (el.src) {
|
||||||
tip.style.transform = `translate(${el.hasAttribute('tip-left') ? 'calc(-100% - 5px)' : '15px'}, ${el.hasAttribute('tip-top') ? '-100%' : '15px'
|
const image = document.createElement('img');
|
||||||
})`;
|
image.src = el.src;
|
||||||
body.appendChild(tip);
|
tip.appendChild(image);
|
||||||
element.onmousemove = (e) => {
|
}
|
||||||
tip.style.left = `${e.x}px`;
|
body.appendChild(tip);
|
||||||
tip.style.top = `${e.y}px`;
|
tip.pointerEvents = 'none';
|
||||||
tip.style.zIndex = 1;
|
element.onmousemove = e => {
|
||||||
tip.style.visibility = "visible";
|
determineTootlTipPosition({
|
||||||
};
|
position: element.getBoundingClientRect(),
|
||||||
element.onmouseleave = (e) => {
|
mouse: { x: e.x, y: e.y },
|
||||||
tip.style.visibility = "hidden";
|
tip
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
element.onmouseleave = e => {
|
||||||
|
tip.style.visibility = 'hidden';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(document.body.querySelectorAll('[tip]')).forEach(el => {
|
||||||
|
addSingleTooltip(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
function showChatMessage(article) {
|
function showChatMessage(article) {
|
||||||
document.querySelector('#chatBox').appendChild(article);
|
let body = null;
|
||||||
const messages = Array.from(document.body.querySelectorAll('.msg-container'));
|
if (article !== undefined) {
|
||||||
const lastMessage = messages[messages.length - 1];
|
body = document.getElementById('chatBox');
|
||||||
lastMessage.scrollIntoView({ behavior: 'smooth' });
|
body.appendChild(article);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = document.body.querySelectorAll('.msg-container');
|
||||||
|
|
||||||
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' });
|
||||||
|
|
||||||
|
// const messageId = article.id;
|
||||||
|
// languageElement.setAttribute('id', article.id);
|
||||||
|
|
||||||
|
// 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 minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||||
const time = `${hours}:${minutes}`;
|
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
||||||
|
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', "#TWITCH_CLIENT_ID");
|
hideText('.password-toggle-btn2', '#TROVO_OAUTH_TOKEN');
|
||||||
hideText('.password-toggle-btn3', "#TWITCH_CLIENT_SECRET");
|
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");
|
|
||||||
|
|
||||||
// Amazon TTS
|
function setZoomLevel(currentZoom, zoomIn) {
|
||||||
// const polly = new Polly(amazonCredentials);
|
let newZoom = currentZoom.toFixed(2);
|
||||||
// const options = {
|
|
||||||
// text: 'Hallo mijn naam is KEES',
|
if (zoomIn === true && currentZoom < 4.95) {
|
||||||
// voiceId: 'Lotte',
|
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
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const fileStream = fs.createWriteStream(path.join(__dirname, '/public/sounds/tts/Amazon_audio.mp3'));
|
// navigator.mediaDevices
|
||||||
|
// .getUserMedia(settingxs)
|
||||||
// polly.textToSpeech(options, (err, audioStream) => {
|
// .then(stream => {
|
||||||
// if (err) {
|
// const video = document.getElementById('video');
|
||||||
// return console.warn(err.message);
|
// video.srcObject = stream;
|
||||||
// }
|
// video.play();
|
||||||
// audioStream.pipe(fileStream);
|
// })
|
||||||
// return 1;
|
// .catch(err => {
|
||||||
// });
|
// console.log(err);
|
||||||
|
// alert('Não há permissões para acessar a webcam.');
|
||||||
|
// });
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
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') {
|
}
|
||||||
return 'scissors';
|
if (input === 'paper') {
|
||||||
} if (input === 'scissors') {
|
return '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') {
|
}
|
||||||
return 'Talk to you later!';
|
if (input === 'goodbye') {
|
||||||
}
|
return 'Talk to you later!';
|
||||||
return 'Try asking something else!';
|
}
|
||||||
|
return 'Try asking something else!';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
src/js/server.js
|
|
@ -1,54 +1,121 @@
|
||||||
const express = require('express');
|
/* global settings */
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http').createServer(app);
|
const http = require('http');
|
||||||
const io = require('socket.io')(http);
|
const localServer = http.createServer(app);
|
||||||
|
const io = require('socket.io')(localServer);
|
||||||
|
|
||||||
if (!config.settings.SERVER.USE_SERVER) {
|
const requestCount = 0;
|
||||||
return;
|
|
||||||
|
function startVtuberModule() {
|
||||||
|
if (!settings.MODULES.USE_VTUBER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
|
||||||
|
|
||||||
|
const vtuber = document.body.querySelector('#BrowsersourceVtuber');
|
||||||
|
const vtuberframe = document.createElement('iframe');
|
||||||
|
vtuberframe.class = 'frame';
|
||||||
|
vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`;
|
||||||
|
vtuberframe.style.width = '100%';
|
||||||
|
vtuberframe.style.height = '100%';
|
||||||
|
vtuberframe.frameBorder = 0;
|
||||||
|
vtuber.appendChild(vtuberframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PORT = config.settings.SERVER.PORT;
|
startVtuberModule();
|
||||||
|
|
||||||
http.listen(PORT, () => {
|
function startPNGtuberModule() {
|
||||||
if (config.settings.SERVER.USE_VTUBER) {
|
if (!settings.MODULES.USE_PNGTUBER) {
|
||||||
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let vtuber = document.body.querySelector('#BrowsersourceVtuber');
|
app.use('/pngtuber', express.static(path.join(__dirname, '../modules/pngtuber/')));
|
||||||
let vtuberframe = document.createElement('iframe');
|
|
||||||
vtuberframe.class = "frame";
|
|
||||||
vtuberframe.src = `http://localhost:${PORT}/vtuber`;
|
|
||||||
vtuberframe.style.width = "100%";
|
|
||||||
vtuberframe.style.height = "100%";
|
|
||||||
vtuberframe.frameBorder = 0;
|
|
||||||
vtuber.appendChild(vtuberframe);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.settings.SERVER.USE_CHATBUBBLE) {
|
const pngtuber = document.body.querySelector('#BrowsersourcePNGTuber');
|
||||||
app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
let chat = document.body.querySelector('#BrowsersourceChat');
|
startPNGtuberModule();
|
||||||
let chatframe = document.createElement('iframe');
|
|
||||||
chatframe.class = "frame";
|
function startChatWindowModule() {
|
||||||
chatframe.src = `http://localhost:${PORT}/chat`;
|
if (!settings.MODULES.USE_CHATBUBBLE) {
|
||||||
chatframe.style.width = "100%";
|
return;
|
||||||
chatframe.style.height = "100%";
|
}
|
||||||
chatframe.frameBorder = 0;
|
|
||||||
chat.appendChild(chatframe);
|
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() {
|
||||||
|
if (!settings.MODULES.USE_CHATBUBBLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/chatbubble', express.static(path.join(__dirname, '../modules/chatbubble')));
|
||||||
|
|
||||||
|
const chatBubble = document.body.querySelector('#BrowsersourceChatBubble');
|
||||||
|
const chatBubbleFrame = document.createElement('iframe');
|
||||||
|
chatBubbleFrame.class = 'frame';
|
||||||
|
chatBubbleFrame.src = `http://localhost:${settings.GENERAL.PORT}/chatbubble`;
|
||||||
|
chatBubbleFrame.style.width = '100%';
|
||||||
|
chatBubbleFrame.style.height = '100%';
|
||||||
|
chatBubbleFrame.frameBorder = 0;
|
||||||
|
chatBubble.appendChild(chatBubbleFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
startChatBubbleModule();
|
||||||
|
|
||||||
|
// Middleware to conditionally serve routes
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') {
|
||||||
|
res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled
|
||||||
|
} else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') {
|
||||||
|
res.sendStatus(404); // Return a 404 status for /chat when it's disabled
|
||||||
|
} else {
|
||||||
|
next(); // Proceed to the next middleware or route handler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
localServer.listen(settings.GENERAL.PORT, () => {
|
||||||
|
startVtuberModule();
|
||||||
|
startChatWindowModule();
|
||||||
|
startChatBubbleModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle socket connections
|
// Handle socket connections
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', socket => {
|
||||||
|
// Receive data from the client
|
||||||
|
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('message', (data) => { });
|
socket.on('xxx', (logoUrl, username, message) => {
|
||||||
|
socket.broadcast.emit('message', logoUrl, username, message);
|
||||||
|
});
|
||||||
|
|
||||||
// Receive data from the client
|
socket.on('disconnect', () => {});
|
||||||
socket.on('xxx', (logoUrl, username, message) => {
|
|
||||||
socket.broadcast.emit('message', logoUrl, username, message);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', () => { });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports = { startVtuberModule, startChatBubbleModule, startChatWindowModule };
|
||||||
|
|
|
||||||
1012
src/js/settings.js
181
src/js/sound.js
|
|
@ -1,78 +1,143 @@
|
||||||
let trueMessage = '';
|
/* global ttsAudioFile, main, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */
|
||||||
let currentLogoUrl = '';
|
|
||||||
let currentUsername = '';
|
|
||||||
let voiceSoundArray = [];
|
|
||||||
let status = 0;
|
|
||||||
|
|
||||||
const playTTS = (ttsData) => new Promise((resolve) => {
|
const voiceSoundArray = [];
|
||||||
const tts = new Audio(ttsData.path);
|
let status = 0;
|
||||||
|
const counter = 0;
|
||||||
|
|
||||||
|
const playTTS = data =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
ttsAudioFile = path.join(
|
||||||
|
resourcesPath,
|
||||||
|
main.isPackaged ? `./sounds/${data.service}_${data.count}.mp3` : `../sounds/${data.service}_${data.count}.mp3`
|
||||||
|
);
|
||||||
|
const tts = new Audio(ttsAudioFile);
|
||||||
|
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
|
||||||
|
|
||||||
tts.addEventListener('ended', () => {
|
tts.addEventListener('ended', () => {
|
||||||
fs.unlink(ttsData.path, (err) => {
|
// console.log('ended');
|
||||||
if (err) {
|
fs.unlink(ttsAudioFile, err => {
|
||||||
console.error(err);
|
if (err) {
|
||||||
return;
|
console.error(err);
|
||||||
}
|
resolve('finished');
|
||||||
resolve('finished');
|
return;
|
||||||
});
|
}
|
||||||
|
resolve('finished');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tts.setSinkId(config.settings.AUDIO.TTS_AUDIO_DEVICE).then(() => {
|
tts
|
||||||
tts.play();
|
.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
|
||||||
socket.emit('xxx', currentLogoUrl, currentUsername, ttsData.message);
|
.then(() => {
|
||||||
}).catch((error) => {
|
// 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);
|
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(ttsData) {
|
function add(data) {
|
||||||
voiceSoundArray.push(ttsData);
|
voiceSoundArray.push(data);
|
||||||
if (status === 0) {
|
if (status === 0) {
|
||||||
shiftVoice();
|
shiftVoice();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playNotificationSound() {
|
||||||
|
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
|
||||||
|
const notfication = new Audio(
|
||||||
|
path.join(
|
||||||
|
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.play().catch(error => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to set audio output device:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play sound function
|
// Play sound function
|
||||||
function playAudio(ttsData = undefined) {
|
function playAudio(data) {
|
||||||
let audioPath;
|
if (data.service !== '') {
|
||||||
if (!ttsData) {
|
add(data);
|
||||||
let notfication = undefined;
|
}
|
||||||
if (envInfo.env) {
|
|
||||||
notfication = new Audio(path.join(envInfo.path, `./sounds/notifications/${notificationSound.options[config.settings.AUDIO.NOTIFICATION_SOUND].text}`));
|
|
||||||
} else {
|
|
||||||
notfication = new Audio(path.join(__dirname, `../sounds/notifications/${notificationSound.options[config.settings.AUDIO.NOTIFICATION_SOUND].text}`));
|
|
||||||
}
|
|
||||||
notfication.play();
|
|
||||||
} else {
|
|
||||||
add(ttsData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function playVoice(filteredMessage, logoUrl, username, message) {
|
async function playVoice(message) {
|
||||||
trueMessage = filteredMessage;
|
if (!settings.TTS.PRIMARY_VOICE) {
|
||||||
currentLogoUrl = logoUrl;
|
return;
|
||||||
currentUsername = username;
|
}
|
||||||
let textObject = { "filtered": filteredMessage, "formatted": message };
|
const textObject = { filtered: message.filteredMessage, formatted: message.formattedMessage };
|
||||||
let voice;
|
let voice = settings.TTS.PRIMARY_VOICE;
|
||||||
const language = langdetect.detect(filteredMessage);
|
textObject.filtered = `${message.username}: ${message.filteredMessage}`;
|
||||||
|
|
||||||
if (language[0].lang === config.settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase()) {
|
if (
|
||||||
voice = config.settings.TTS.SECONDARY_TTS_NAME;
|
settings.LANGUAGE.USE_DETECTION &&
|
||||||
textObject.filtered = `${username}: ${filteredMessage}`;
|
settings.TTS.SECONDARY_VOICE &&
|
||||||
} else {
|
message.filteredMessage.length > 20 &&
|
||||||
voice = config.settings.TTS.PRIMARY_TTS_NAME;
|
countWords(message.filteredMessage) > 4
|
||||||
textObject.filtered = `${username}: ${filteredMessage}`;
|
) {
|
||||||
|
const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE);
|
||||||
|
if (message.language.detectedLanguage === null || message.language.detectedLanguage.ISO639 === secondaryTTSLanguage.ISO639) {
|
||||||
|
voice = settings.TTS.SECONDARY_VOICE;
|
||||||
|
textObject.filtered = message.originalMessage ? message.originalMessage : message.filteredMessage;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
talk.add(textObject, voice);
|
const service = document.getElementById('primaryTTSService').value;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
socket.emit('xxx', message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { playAudio, playVoice };
|
module.exports = { playAudio, playVoice, playNotificationSound };
|
||||||
|
|
|
||||||
206
src/js/theme.js
|
|
@ -1,179 +1,143 @@
|
||||||
function setTheme(USE_CUSTOM_THEME) {
|
/* global settings, root, fs, settingsPath, ini */
|
||||||
document.querySelector('#MAIN_COLOR_1').value = config.settings.THEME.MAIN_COLOR_1;
|
|
||||||
const MAIN_COLOR_1 = document.querySelector('#MAIN_COLOR_1').value;
|
|
||||||
root.style.setProperty('--main-color1-temp', MAIN_COLOR_1);
|
|
||||||
|
|
||||||
document.querySelector('#MAIN_COLOR_2').value = config.settings.THEME.MAIN_COLOR_2;
|
function changeColor(section, setting, tempSection) {
|
||||||
const MAIN_COLOR_2 = document.querySelector('#MAIN_COLOR_2').value;
|
document.querySelector(section).value = setting;
|
||||||
root.style.setProperty('--main-color2-temp', MAIN_COLOR_2);
|
const value = document.querySelector(section).value;
|
||||||
|
root.style.setProperty(tempSection, value);
|
||||||
document.querySelector('#MAIN_COLOR_3').value = config.settings.THEME.MAIN_COLOR_3;
|
}
|
||||||
const MAIN_COLOR_3 = document.querySelector('#MAIN_COLOR_3').value;
|
|
||||||
root.style.setProperty('--main-color3-temp', MAIN_COLOR_3);
|
function setCurrentTheme(adjustTemp = false) {
|
||||||
|
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1');
|
||||||
document.querySelector('#MAIN_COLOR_4').value = config.settings.THEME.MAIN_COLOR_4;
|
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2');
|
||||||
const MAIN_COLOR_4 = document.querySelector('#MAIN_COLOR_4').value;
|
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3');
|
||||||
root.style.setProperty('--main-color4-temp', MAIN_COLOR_4);
|
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');
|
||||||
document.querySelector('#TOP_BAR').value = config.settings.THEME.TOP_BAR;
|
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section');
|
||||||
const TOP_BAR = document.querySelector('#TOP_BAR').value;
|
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble');
|
||||||
root.style.setProperty('--top-bar-temp', TOP_BAR);
|
changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header');
|
||||||
|
changeColor(
|
||||||
document.querySelector('#MID_SECTION').value = config.settings.THEME.MID_SECTION;
|
'#CHAT_BUBBLE_MESSAGE',
|
||||||
const MID_SECTION = document.querySelector('#MID_SECTION').value;
|
settings.THEME.CHAT_BUBBLE_MESSAGE,
|
||||||
root.style.setProperty('--mid-section-temp', MID_SECTION);
|
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message'
|
||||||
|
);
|
||||||
document.querySelector('#CHAT_BUBBLE_BG').value = config.settings.THEME.CHAT_BUBBLE_BG;
|
}
|
||||||
const CHAT_BUBBLE_BG = document.querySelector('#CHAT_BUBBLE_BG').value;
|
|
||||||
root.style.setProperty('--chat-bubble-temp', CHAT_BUBBLE_BG);
|
setCurrentTheme(true);
|
||||||
|
|
||||||
document.querySelector('#CHAT_BUBBLE_HEADER').value = config.settings.THEME.CHAT_BUBBLE_HEADER;
|
function setTheme() {
|
||||||
const CHAT_BUBBLE_HEADER = document.querySelector('#CHAT_BUBBLE_HEADER').value;
|
if (settings.THEME.USE_CUSTOM_THEME) {
|
||||||
root.style.setProperty('--chat-bubble-header-temp', CHAT_BUBBLE_HEADER);
|
setCurrentTheme();
|
||||||
|
} else {
|
||||||
document.querySelector('#CHAT_BUBBLE_MESSAGE').value = config.settings.THEME.CHAT_BUBBLE_MESSAGE;
|
root.style.setProperty('--main-color1', '#6e2c8c');
|
||||||
const CHAT_BUBBLE_MESSAGE = document.querySelector('#CHAT_BUBBLE_MESSAGE').value;
|
root.style.setProperty('--main-color2', 'white');
|
||||||
root.style.setProperty('--chat-bubble-message-temp', CHAT_BUBBLE_MESSAGE);
|
root.style.setProperty('--main-color3', '#211E1E');
|
||||||
|
root.style.setProperty('--main-color4', '#2f2c34');
|
||||||
if (USE_CUSTOM_THEME) {
|
root.style.setProperty('--top-bar', '#100B12');
|
||||||
root.style.setProperty('--main-color1', MAIN_COLOR_1);
|
root.style.setProperty('--mid-section', '#352d3d');
|
||||||
|
root.style.setProperty('--chat-bubble', ' #7A6D7F');
|
||||||
root.style.setProperty('--main-color2', MAIN_COLOR_2);
|
root.style.setProperty('--chat-bubble-header', '#141414');
|
||||||
|
root.style.setProperty('--chat-bubble-message', 'white');
|
||||||
root.style.setProperty('--main-color3', MAIN_COLOR_3);
|
}
|
||||||
|
|
||||||
root.style.setProperty('--main-color4', MAIN_COLOR_4);
|
|
||||||
|
|
||||||
root.style.setProperty('--top-bar', TOP_BAR);
|
|
||||||
|
|
||||||
root.style.setProperty('--mid-section', MID_SECTION);
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble', CHAT_BUBBLE_BG);
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble-header', CHAT_BUBBLE_HEADER);
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble-message', CHAT_BUBBLE_MESSAGE);
|
|
||||||
} else {
|
|
||||||
root.style.setProperty('--main-color1', '#6e2c8c');
|
|
||||||
|
|
||||||
root.style.setProperty('--main-color2', 'white');
|
|
||||||
|
|
||||||
root.style.setProperty('--main-color3', '#211E1E');
|
|
||||||
|
|
||||||
root.style.setProperty('--main-color4', '#2f2c34');
|
|
||||||
|
|
||||||
root.style.setProperty('--top-bar', '#100B12');
|
|
||||||
|
|
||||||
root.style.setProperty('--mid-section', '#352d3d');
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble', ' #7A6D7F');
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble-header', '#141414');
|
|
||||||
|
|
||||||
root.style.setProperty('--chat-bubble-message', 'white');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region Save Theme
|
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => {
|
document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => {
|
||||||
config.settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value;
|
settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value;
|
settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value;
|
settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value;
|
settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value;
|
settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value;
|
settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value;
|
settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
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', () => {
|
||||||
config.settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
||||||
fs.writeFileSync(path.join(__dirname, '../config/settings.ini'), config.ini.stringify(config.settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
setTheme(config.settings.THEME.USE_CUSTOM_THEME);
|
changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message');
|
||||||
});
|
});
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
module.exports = { setTheme };
|
module.exports = { setTheme };
|
||||||
|
|
|
||||||
392
src/js/token-autocomplete.js
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
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 };
|
||||||
344
src/js/trovo.js
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
/* 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 };
|
||||||
599
src/js/twitch.js
|
|
@ -1,146 +1,507 @@
|
||||||
const tmi = require('tmi.js');
|
/* 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 axios = require('axios');
|
|
||||||
|
|
||||||
const client = new tmi.Client({
|
const tmi = require('tmi.js');
|
||||||
options: {
|
|
||||||
skipUpdatingEmotesets: true,
|
let client = null;
|
||||||
},
|
let logoUrl = null;
|
||||||
identity: {
|
const twitchChannels = [];
|
||||||
username: config.settings.TWITCH.USERNAME,
|
|
||||||
password: config.settings.TWITCH.OAUTH_TOKEN,
|
|
||||||
},
|
|
||||||
channels: [config.settings.TWITCH.CHANNEL_NAME],
|
|
||||||
});
|
|
||||||
|
|
||||||
function sendMessage(message) {
|
function sendMessage(message) {
|
||||||
client.say(config.settings.TWITCH.CHANNEL_NAME, message).catch(console.error);
|
client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
client.connect().catch(console.error);
|
if (settings.TWITCH.USERNAME && settings.TWITCH.OAUTH_TOKEN) {
|
||||||
|
client = new tmi.Client({
|
||||||
|
options: {
|
||||||
|
skipUpdatingEmotesets: true
|
||||||
|
},
|
||||||
|
identity: {
|
||||||
|
username: settings.TWITCH.USERNAME,
|
||||||
|
password: settings.TWITCH.OAUTH_TOKEN
|
||||||
|
},
|
||||||
|
channels: [settings.TWITCH.CHANNEL_NAME]
|
||||||
|
});
|
||||||
|
|
||||||
function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage) {
|
client
|
||||||
const article = document.createElement('article');
|
.connect()
|
||||||
article.className = 'msg-container msg-remote';
|
.then(data => {})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
article.innerHTML = messageTemplates.twitchTemplate;
|
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;
|
||||||
|
|
||||||
const userImg = article.querySelector('.icon-container > .user-img');
|
emoteValues.forEach(entry => {
|
||||||
if (userImg) {
|
entry[1].forEach(lol => {
|
||||||
userImg.src = logoUrl;
|
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 usernameHtml = article.querySelector('.username');
|
const messageObject = parseString(emoteMessage);
|
||||||
if (usernameHtml) {
|
|
||||||
usernameHtml.innerText = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
const postTime = article.querySelector('.post-time');
|
getProfileImage(tags['user-id'], tags['display-name'], messageObject, filteredMessage);
|
||||||
if (postTime) {
|
});
|
||||||
postTime.innerText = getPostTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg = article.querySelector('.msg');
|
|
||||||
if (msg) {
|
|
||||||
msg.innerHTML = "";
|
|
||||||
|
|
||||||
const messageElement = document.createElement("div");
|
|
||||||
|
|
||||||
messageObject.forEach((entry) => {
|
|
||||||
const messageElement = document.createElement("div");
|
|
||||||
if (entry.text) {
|
|
||||||
messageElement.innerText = entry.text;
|
|
||||||
msg.appendChild(messageElement);
|
|
||||||
} else {
|
|
||||||
messageElement.innerHTML = entry.html;
|
|
||||||
msg.appendChild(messageElement);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the message to the main chat box (shows the message)
|
|
||||||
showChatMessage(article);
|
|
||||||
|
|
||||||
if (fileteredMessage) {
|
|
||||||
sound.playVoice(fileteredMessage, logoUrl, username, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.article = article;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfileImage(userid, username, message, fileteredMessage) {
|
function checkIfTokenIsValid() {
|
||||||
// Get Access Token
|
options = {
|
||||||
let options = {
|
method: 'GET',
|
||||||
method: 'POST',
|
url: 'https://id.twitch.tv/oauth2/validate',
|
||||||
url: 'https://id.twitch.tv/oauth2/token',
|
headers: {
|
||||||
data: {
|
Authorization: `OAuth ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
grant_type: 'client_credentials',
|
}
|
||||||
client_Id: config.settings.TWITCH.CLIENT_ID,
|
};
|
||||||
client_Secret: config.settings.TWITCH.CLIENT_SECRET,
|
|
||||||
audience: 'YOUR_API_IDENTIFIER',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
axios.request(options).then((responseAccessToken) => {
|
axios
|
||||||
const accessToken = responseAccessToken.data.access_token;
|
.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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get user Logo with access token
|
setInterval(checkIfTokenIsValid, 600000);
|
||||||
options = {
|
|
||||||
method: 'GET',
|
|
||||||
url: `https://api.twitch.tv/helix/users?id=${userid}`,
|
|
||||||
headers: { 'Client-ID': config.settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${accessToken}` },
|
|
||||||
};
|
|
||||||
|
|
||||||
axios.request(options).then((responseLogoUrl) => {
|
if (settings.TWITCH.OAUTH_TOKEN) {
|
||||||
const logoUrl = responseLogoUrl.data.data[0].profile_image_url;
|
checkIfTokenIsValid();
|
||||||
displayTwitchMessage(logoUrl, username, message, fileteredMessage);
|
} else {
|
||||||
}).catch((error) => {
|
document.getElementById('TWITCH_CHANNEL_NAME').disabled = true;
|
||||||
console.error(error);
|
}
|
||||||
});
|
|
||||||
}).catch((error) => {
|
function ping(element) {
|
||||||
console.error(error);
|
const value = document.body.querySelector(element);
|
||||||
});
|
|
||||||
|
client
|
||||||
|
.ping()
|
||||||
|
.then(data => {
|
||||||
|
value.classList.add('success');
|
||||||
|
value.innerText = 'Success!';
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
value.classList.add('error');
|
||||||
|
value.innerText = 'Failed!';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function displayTwitchMessage(logoUrl, username, messageObject, filteredMessage) {
|
||||||
|
messageId++;
|
||||||
|
const article = document.createElement('article');
|
||||||
|
article.className = 'msg-container sender';
|
||||||
|
article.setAttribute('id', messageId);
|
||||||
|
|
||||||
|
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');
|
||||||
|
if (usernameHtml) {
|
||||||
|
usernameHtml.innerText = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sound.playNotificationSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProfileImage(userid, username, message, filteredMessage) {
|
||||||
|
// Get user Logo with access token
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://api.twitch.tv/helix/users?id=${userid}`,
|
||||||
|
headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` }
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(responseLogoUrl => {
|
||||||
|
logoUrl = responseLogoUrl.data.data[0].profile_image_url;
|
||||||
|
displayTwitchMessage(logoUrl, username, message, filteredMessage);
|
||||||
|
})
|
||||||
|
.catch(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")) {
|
}
|
||||||
result.push({ text: match });
|
if (match !== '' && !match.startsWith('<img')) {
|
||||||
}
|
result.push({ text: match });
|
||||||
}
|
}
|
||||||
return result;
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on('message', (channel, tags, message, self) => {
|
function formatTwitchEmotes(channel) {
|
||||||
if (self) {
|
if (channel.emotes.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emotes = tags.emotes || {};
|
channel.emotes.forEach(emote => {
|
||||||
const emoteValues = Object.entries(emotes);
|
if (channel.name !== 'Twitch Global' && emote.emote_type === 'bitstier') {
|
||||||
let fileteredMessage = message;
|
return;
|
||||||
let emoteMessage = message;
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
emoteValues.forEach((entry) => {
|
function formatBetterTtvEmotes(data) {
|
||||||
entry[1].forEach((lol) => {
|
if (data.emotes.length === 0) {
|
||||||
const [start, end] = lol.split('-');
|
return;
|
||||||
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);
|
|
||||||
fileteredMessage = fileteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let messageObject = parseString(emoteMessage)
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
sound.playAudio();
|
function getBetterTtvGLobalEmotes() {
|
||||||
getProfileImage(tags['user-id'], tags['display-name'], messageObject, fileteredMessage);
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { sendMessage };
|
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}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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() {
|
||||||
|
// Get user Logo with access token
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.twitch.tv/helix/chat/emotes/global',
|
||||||
|
headers: {
|
||||||
|
'Client-ID': settings.TWITCH.CLIENT_ID,
|
||||||
|
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(responseLogoUrl => {
|
||||||
|
formatTwitchEmotes({ broadcaster_name: 'Global', emotes: responseLogoUrl.data.data });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserAvailableTwitchEmotes() {
|
||||||
|
if (settings.TWITCH.OAUTH_TOKEN) {
|
||||||
|
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 =>
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
let SelectedVoice = '';
|
|
||||||
let Encoding = '';
|
|
||||||
let counter = 0;
|
|
||||||
// wrap in promise
|
|
||||||
const speak = (textObject) => new Promise((resolve) => {
|
|
||||||
// say.setEncoding(Encoding);
|
|
||||||
counter += 1;
|
|
||||||
let savePath = '';
|
|
||||||
|
|
||||||
if (envInfo.env) {
|
|
||||||
savePath = path.join(envInfo.path, './sounds/tts/internal_audio_' + counter + '.mp3')
|
|
||||||
} else {
|
|
||||||
savePath = path.join(__dirname, '../sounds/tts/internal_audio_' + counter + '.mp3')
|
|
||||||
}
|
|
||||||
|
|
||||||
say.export(textObject.filtered, SelectedVoice, 1, savePath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
} else {
|
|
||||||
sound.playAudio({ "path": savePath, "message": textObject });
|
|
||||||
}
|
|
||||||
resolve('finished');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// queue system
|
|
||||||
class SayQueue {
|
|
||||||
constructor() {
|
|
||||||
this.messages = [];
|
|
||||||
this.status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async shift() {
|
|
||||||
this.status = 1;
|
|
||||||
while (this.messages.length > 0) {
|
|
||||||
await speak(this.messages.shift(), SelectedVoice, 1);
|
|
||||||
}
|
|
||||||
this.status = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(message, selectedVoice) {
|
|
||||||
this.messages.push(message);
|
|
||||||
SelectedVoice = selectedVoice;
|
|
||||||
if (this.status === 0) { this.shift(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sayQueue = new SayQueue();
|
|
||||||
module.exports = sayQueue;
|
|
||||||
96
src/js/youtube.js
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
384
src/main.js
|
|
@ -1,179 +1,279 @@
|
||||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
/* global pythonPath, a */
|
||||||
const { writeIniFile } = require('write-ini-file')
|
|
||||||
const path = require('path');
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const http = require('http');
|
||||||
|
const kill = require('kill-process-by-name');
|
||||||
|
|
||||||
const ini = require('ini');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let resourcesPath;
|
let resourcesPath = __dirname;
|
||||||
|
let settingsPath = null;
|
||||||
|
|
||||||
let settings;
|
let settings;
|
||||||
let window;
|
let window;
|
||||||
|
|
||||||
if (app.isPackaged) {
|
if (app.isPackaged) {
|
||||||
resourcesPath = path.join(process.resourcesPath, './settings.ini');
|
settingsPath = path.join(process.resourcesPath, './settings.json');
|
||||||
|
pythonPath = path.join(process.resourcesPath, './backend');
|
||||||
|
resourcesPath = process.resourcesPath;
|
||||||
} else {
|
} else {
|
||||||
resourcesPath = path.join(__dirname, './config/settings.ini');
|
settingsPath = path.join(resourcesPath, './config/settings.json');
|
||||||
}
|
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(resourcesPath)) {
|
if (!fs.existsSync(settingsPath)) {
|
||||||
await createIniFile(resourcesPath);
|
settings = await createSettingsFile();
|
||||||
} else {
|
} else {
|
||||||
settings = ini.parse(fs.readFileSync(resourcesPath, 'utf-8'));
|
const file = fs.readFileSync(settingsPath);
|
||||||
}
|
settings = JSON.parse(file);
|
||||||
|
}
|
||||||
|
|
||||||
window = new BrowserWindow({
|
window = new BrowserWindow({
|
||||||
icon: path.join(__dirname, '/images/icon.png'),
|
icon: path.join(__dirname, '/images/icon-512.png'),
|
||||||
width: parseInt(settings.SETTINGS.WIDTH),
|
width: parseInt(settings.GENERAL.WIDTH),
|
||||||
height: parseInt(settings.SETTINGS.HEIGHT),
|
height: parseInt(settings.GENERAL.HEIGHT),
|
||||||
x: parseInt(settings.SETTINGS.POSITION_X),
|
x: parseInt(settings.GENERAL.POSITION_X),
|
||||||
y: parseInt(settings.SETTINGS.POSITION_Y),
|
y: parseInt(settings.GENERAL.POSITION_Y),
|
||||||
frame: false,
|
frame: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
enableRemoteModule: true,
|
enableRemoteModule: true
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
window.loadURL('https://github.com')
|
window.loadFile(path.join(__dirname, 'index.html'));
|
||||||
|
|
||||||
window.loadFile(path.join(__dirname, 'index.html'));
|
// Create the second window
|
||||||
|
const secondWindow = new BrowserWindow({
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
// parent: window, // Set the parent window
|
||||||
|
// modal: true,
|
||||||
|
frame: false,
|
||||||
|
show: true, // Hide the second window initially if needed
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
enableRemoteModule: true,
|
||||||
|
backgroundThrottling: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!app.isPackaged) {
|
// Load second.html into the second window
|
||||||
window.webContents.openDevTools();
|
secondWindow.loadFile(path.join(__dirname, './modules/facemask/index.html'));
|
||||||
}
|
// secondWindow.webContents.setFrameRate(60);
|
||||||
|
|
||||||
window.on('close', e => {
|
if (!app.isPackaged) {
|
||||||
settings = ini.parse(fs.readFileSync(resourcesPath, 'utf-8')); // load newest settings in case anything changed after starting the program
|
window.webContents.openDevTools();
|
||||||
const bounds = window.getBounds();
|
secondWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
settings.SETTINGS.WIDTH = bounds.width;
|
window.on('close', e => {
|
||||||
settings.SETTINGS.HEIGHT = bounds.height;
|
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program
|
||||||
settings.SETTINGS.POSITION_X = bounds.x;
|
const bounds = window.getBounds();
|
||||||
settings.SETTINGS.POSITION_Y = bounds.y;
|
|
||||||
|
|
||||||
fs.writeFileSync(resourcesPath, ini.stringify(settings));
|
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') {
|
||||||
app.quit();
|
// kill('loquendoBot_backend');
|
||||||
}
|
app.quit();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
// On OS X it's common to re-create a window in the app when the
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
// dock icon is clicked and there are no other windows open.
|
createWindow();
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
}
|
||||||
createWindow();
|
});
|
||||||
}
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
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) => {
|
ipcMain.on('close-window', event => {
|
||||||
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
browserWindow.close();
|
kill('loquendoBot_backend');
|
||||||
app.quit();
|
browserWindow.close();
|
||||||
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('environment', (event) => {
|
ipcMain.on('restart', event => {
|
||||||
event.returnValue = { "env": app.isPackaged, "path": process.resourcesPath };
|
app.relaunch();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createIniFile() {
|
ipcMain.on('environment', event => {
|
||||||
await writeIniFile(resourcesPath, {
|
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
|
||||||
SETTINGS: {
|
});
|
||||||
VOICE_ENABLED: true,
|
|
||||||
NOTIFICATION_ENABLED: true,
|
async function createSettingsFile() {
|
||||||
POSITION_X: 0,
|
const settingsx = {
|
||||||
POSITION_Y: 0,
|
GENERAL: {
|
||||||
WIDTH: 1024,
|
VOICE_ENABLED: true,
|
||||||
HEIGHT: 768,
|
NOTIFICATION_ENABLED: true,
|
||||||
LANGUAGE: "EN"
|
POSITION_X: 0,
|
||||||
},
|
POSITION_Y: 0,
|
||||||
TTS: {
|
WIDTH: 1024,
|
||||||
PRIMARY_TTS_VOICE: 0,
|
HEIGHT: 768,
|
||||||
PRIMARY_TTS_NAME: "",
|
LANGUAGE: 'none',
|
||||||
PRIMARY_TTS_LANGUAGE: "EN",
|
LANGUAGE_INDEX: '0',
|
||||||
PRIMARY_TTS_LANGUAGE_INDEX: 0,
|
PORT: 9000,
|
||||||
SECONDARY_TTS_VOICE: 0,
|
VIEWERS_PANEL: false,
|
||||||
SECONDARY_TTS_NAME: "",
|
LOCATION: pythonPath,
|
||||||
SECONDARY_TTS_LANGUAGE: "EN",
|
ZOOMLEVEL: 1
|
||||||
SECONDARY_TTS_LANGUAGE_INDEX: 0
|
},
|
||||||
},
|
LANGUAGE: {
|
||||||
AUDIO: {
|
USE_DETECTION: false,
|
||||||
NOTIFICATION_AUDIO_DEVICE: 0,
|
TRANSLATE_TO: 'none',
|
||||||
NOTIFICATION_SOUND: 0,
|
LANGUAGE_INDEX: '0',
|
||||||
NOTIFICATION_VOLUME: 100,
|
BROADCAST_TRANSLATION: false,
|
||||||
SELECTED_TTS_AUDIO_DEVICE: 0,
|
OUTPUT_TO_TTS: false,
|
||||||
TTS_AUDIO_DEVICE: "default",
|
TRANSLATE_TO_INDEX: 0,
|
||||||
TTS_VOLUME: 100
|
SEND_TRANSLATION: false,
|
||||||
},
|
SEND_TRANSLATION_IN: 'none',
|
||||||
THEME: {
|
SEND_TRANSLATION_OUT: 'none'
|
||||||
USE_CUSTOM_THEME: false,
|
},
|
||||||
MAIN_COLOR_1: "\#cdc1c1",
|
TTS: {
|
||||||
MAIN_COLOR_2: "\#b12020",
|
USE_TTS: false,
|
||||||
MAIN_COLOR_3: "\#6c4104",
|
PRIMARY_VOICE: '',
|
||||||
MAIN_COLOR_4: "\#532d2d",
|
PRIMARY_TTS_LANGUAGE: 'none',
|
||||||
TOP_BAR: "\#c8ff00",
|
SECONDARY_VOICE: '',
|
||||||
MID_SECTION: "\#6b8578",
|
SECONDARY_TTS_LANGUAGE: 'none',
|
||||||
CHAT_BUBBLE_BG: "\#447466",
|
PRIMARY_TTS_LANGUAGE_INDEX: 0,
|
||||||
CHAT_BUBBLE_HEADER: "\#ffffff",
|
SECONDARY_TTS_LANGUAGE_INDEX: 0
|
||||||
CHAT_BUBBLE_MESSAGE: "\#b5b5b5",
|
},
|
||||||
},
|
STT: {
|
||||||
TWITCH: {
|
USE_STT: false,
|
||||||
USE_TWITCH: false,
|
MICROPHONE_ID: 'default',
|
||||||
CHANNEL_NAME: "khyretos",
|
SELECTED_MICROPHONE: 'default',
|
||||||
USERNAME: "loquendo",
|
MICROPHONE: 0,
|
||||||
OAUTH_TOKEN: "",
|
LANGUAGE: ''
|
||||||
CLIENT_ID: "",
|
},
|
||||||
CLIENT_SECRET: "",
|
AUDIO: {
|
||||||
},
|
USE_NOTIFICATION_SOUNDS: false,
|
||||||
SERVER: {
|
SELECTED_NOTIFICATION_AUDIO_DEVICE: 'default',
|
||||||
USE_SERVER: false,
|
NOTIFICATION_AUDIO_DEVICE: 0,
|
||||||
PORT: "9000",
|
NOTIFICATION_SOUND: 0,
|
||||||
USE_VTUBER: false,
|
NOTIFICATION_VOLUME: 50,
|
||||||
USE_CHATBUBBLE: false,
|
SELECTED_TTS_AUDIO_DEVICE: 0,
|
||||||
},
|
TTS_AUDIO_DEVICE: 'default',
|
||||||
AMAZON: {
|
TTS_VOLUME: 50
|
||||||
USE_TWITCH: false,
|
},
|
||||||
ACCESS_KEY: "",
|
THEME: {
|
||||||
ACCESS_SECRET: "",
|
USE_CUSTOM_THEME: false,
|
||||||
},
|
MAIN_COLOR_1: '#cdc1c1',
|
||||||
GOOGLE: {
|
MAIN_COLOR_2: '#b12020',
|
||||||
USE_GOOGLE: false,
|
MAIN_COLOR_3: '#6c4104',
|
||||||
API_KEY: "",
|
MAIN_COLOR_4: '#532d2d',
|
||||||
}
|
TOP_BAR: '#c8ff00',
|
||||||
}).then(() => {
|
MID_SECTION: '#6b8578',
|
||||||
settings = ini.parse(fs.readFileSync(resourcesPath, 'utf-8'));
|
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 => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return settingsx;
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
src/modules/chat/images/twitch-icon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,22 +1,20 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<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>
|
|
||||||
<!-- #region Main chat box-->
|
<body>
|
||||||
<div class="OptionPanel show" id="Chat">
|
<div id="chatBox" class="message-window"></div>
|
||||||
<div id="chatBox" class="message-window">
|
<emoji-picker class="dark"></emoji-picker>
|
||||||
<div class="texts"></div>
|
</body>
|
||||||
</div>
|
<script src="main.js"></script>
|
||||||
</div>
|
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
|
||||||
<script src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,598 @@
|
||||||
body {
|
@font-face {
|
||||||
background-color: transparent;
|
font-family: 'FRAMDCN';
|
||||||
font-family: 'FRAMDCN';
|
src: url(../fonts/FRAMCDN/FRAMDCN.woff);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--variable: 2s;
|
overflow: hidden;
|
||||||
--buttonBackground: #bf2c2c;
|
--main-color1: #6e2c8c;
|
||||||
|
--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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thomas {
|
h1 {
|
||||||
position: relative;
|
font-family: 'FRAMDCN';
|
||||||
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(.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
70% {
|
|
||||||
transform: scale(.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bounce-inx {
|
|
||||||
animation: bounce-inx 1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce-inx {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-container {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
align-self: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-window {
|
.message-window {
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 60px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: hidden;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
width: 80%;
|
flex-direction: column-reverse;
|
||||||
margin: auto;
|
font-family: 'FRAMDCN';
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-window::before {
|
|
||||||
content: '';
|
|
||||||
flex: 1 0 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OptionPanel {
|
|
||||||
/* visibility: hidden; */
|
|
||||||
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: 125px;
|
|
||||||
hyphens: auto;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
float: right;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.message {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 2px solid #ff80e1;
|
z-index: 1;
|
||||||
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)); */
|
|
||||||
|
|
||||||
|
.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;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
width: 100%;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #4c4c6a;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatBox {
|
||||||
|
width: 300px;
|
||||||
|
height: 400px;
|
||||||
|
max-height: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0 4px var(--main-color4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-window {
|
||||||
|
flex: auto;
|
||||||
|
max-height: calc(100% - 60px);
|
||||||
|
background: #2f323b;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 60px;
|
||||||
|
background: var(--main-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input input {
|
||||||
|
height: 59px;
|
||||||
|
line-height: 60px;
|
||||||
|
outline: 0 none;
|
||||||
|
border: none;
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
color: var(--chat-bubble-message);
|
||||||
|
text-indent: 10px;
|
||||||
|
font-size: 12pt;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--main-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input button {
|
||||||
|
float: right;
|
||||||
|
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 {
|
||||||
|
box-shadow:
|
||||||
|
0 0 2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input input[good] + button:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 8px 17px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
|
/* filter: brightness(150%); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input input[good] + button path {
|
||||||
|
fill: var(--chat-bubble-message);
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container {
|
||||||
|
direction: ltr;
|
||||||
|
position: static;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0px 0px 0px;
|
||||||
|
display: grid;
|
||||||
|
grid-template: 1fr / 1fr;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container > * {
|
||||||
|
grid-column: 1 / 1;
|
||||||
|
grid-row: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container.sender {
|
||||||
|
place-items: self-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container.user {
|
||||||
|
place-items: self-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-box {
|
||||||
|
background: var(--chat-bubble);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 15px;
|
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 {
|
||||||
|
margin: 25px 25px 0px 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-box.user {
|
||||||
|
text-align: left;
|
||||||
|
margin: 25px 35px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-box-user-temp {
|
||||||
|
background: var(--chat-bubble-temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-img {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
z-index: 5;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages.user {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message::after {
|
/* After slide changes */
|
||||||
|
|
||||||
}
|
.toggle:after {
|
||||||
|
content: '';
|
||||||
.arrow{
|
|
||||||
content: "";
|
|
||||||
border: 2px solid #ff80e1;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
width: 30px;
|
||||||
top: 100%;
|
height: 30px;
|
||||||
transform: translateX(-50%) rotate(180deg);
|
border-radius: 50%;
|
||||||
border-width: 10px;
|
background-color: var(--main-color2);
|
||||||
border-style: solid;
|
left: 5px;
|
||||||
border-color: transparent transparent rgb(255, 128, 225,0.7) transparent;
|
top: 5px;
|
||||||
color: #ff80e1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sender{
|
/* Checkbox checked effect */
|
||||||
color: #ff80e1;
|
|
||||||
font-size: 14pt;
|
.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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,124 +1,89 @@
|
||||||
|
/* 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!');
|
||||||
|
|
||||||
function getPostTime() {
|
async function displayChatMessage(message) {
|
||||||
const d = new Date();
|
const article = document.createElement('article');
|
||||||
document.body.querySelectorAll('.container').innerHTML = d.getHours();
|
article.className = 'msg-container sender';
|
||||||
const hours = d.getHours();
|
article.setAttribute('id', message.messageId);
|
||||||
const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
|
|
||||||
const time = `${hours}:${minutes}`;
|
article.innerHTML = twitchTemplate;
|
||||||
return time;
|
const userImg = article.querySelector('.user-img');
|
||||||
|
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) {
|
||||||
const main = document.querySelector('#chatBox');
|
document.getElementById('chatBox').appendChild(article);
|
||||||
main.appendChild(article);
|
|
||||||
main.scrollTop = main.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
let textStreamContainer;
|
const messages = document.body.querySelectorAll('.msg-container');
|
||||||
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);
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
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('message', (logoUrl, username, message, messageDuration) => {
|
socket.on('chat-in', message => {
|
||||||
displayTwitchMessage(logoUrl, username, message);
|
displayChatMessage(message);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
14
src/modules/chat/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
BIN
src/modules/chatbubble/chatbox.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/modules/chatbubble/fonts/FRAMCDN/FRAMDCN-bg.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/modules/chatbubble/fonts/FRAMCDN/FRAMDCN-thumb.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/modules/chatbubble/fonts/FRAMCDN/FRAMDCN.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/modules/chatbubble/fonts/FRAMCDN/FRAMDCN.woff
Normal file
10
src/modules/chatbubble/fonts/FRAMCDN/font.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
@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 */
|
||||||
21
src/modules/chatbubble/fonts/FRAMCDN/index.html
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!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>
|
||||||
2
src/modules/chatbubble/fonts/FRAMCDN/readme.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Original download page
|
||||||
|
http://ttfonts.net/font/606_FranklinGothicMediumCond.htm
|
||||||
23
src/modules/chatbubble/index.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!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>
|
||||||
174
src/modules/chatbubble/main.css
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||