Compare commits
21 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 |
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'
|
||||||
|
}
|
||||||
|
};
|
||||||
8
.gitignore
vendored
|
|
@ -104,4 +104,10 @@ src/sounds/tts/*
|
||||||
loquendoBot_backend.spec
|
loquendoBot_backend.spec
|
||||||
forge.config.js
|
forge.config.js
|
||||||
backend/*
|
backend/*
|
||||||
src/backend/loquendoBot_backend.exe
|
!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
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
from flask import Flask, Response, jsonify, request
|
from flask import Flask, Response, jsonify, request
|
||||||
import gevent
|
import gevent
|
||||||
|
|
||||||
|
import re
|
||||||
import gevent.monkey
|
import gevent.monkey
|
||||||
import json
|
import json
|
||||||
|
from waitress import serve
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("waitress")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
gevent.monkey.patch_all()
|
gevent.monkey.patch_all()
|
||||||
import gevent.queue
|
# import gevent.queue
|
||||||
|
|
||||||
import configparser
|
import configparser
|
||||||
import pyttsx3
|
import pyttsx3
|
||||||
|
|
@ -21,25 +28,29 @@ from deep_translator import (
|
||||||
MyMemoryTranslator,
|
MyMemoryTranslator,
|
||||||
)
|
)
|
||||||
|
|
||||||
import emoji
|
|
||||||
|
|
||||||
from vosk import Model, KaldiRecognizer, SetLogLevel
|
from vosk import Model, KaldiRecognizer, SetLogLevel
|
||||||
|
|
||||||
# global variables
|
# global variables
|
||||||
|
|
||||||
SetLogLevel(-1)
|
SetLogLevel(-1)
|
||||||
|
|
||||||
settings = configparser.ConfigParser()
|
settings = None;
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
settingsPath = os.path.normpath(sys.argv[1])
|
settingsPath = os.path.normpath(sys.argv[1])
|
||||||
|
|
||||||
environment = sys.argv[2]
|
environment = sys.argv[2]
|
||||||
|
|
||||||
q = queue.Queue()
|
q = queue.Queue()
|
||||||
|
|
||||||
# gobal functions
|
# gobal functions
|
||||||
|
|
||||||
|
def loadSettings():
|
||||||
|
with open(settingsPath, 'r') as file:
|
||||||
|
global settings
|
||||||
|
settings = json.load(file)
|
||||||
|
|
||||||
# classes
|
# classes
|
||||||
class LanguageDetection:
|
class LanguageDetection:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -56,19 +67,18 @@ class LanguageDetection:
|
||||||
resources_folder, "language_detection_model", f"lid.176.bin"
|
resources_folder, "language_detection_model", f"lid.176.bin"
|
||||||
)
|
)
|
||||||
|
|
||||||
language_detection_model = (
|
language_detection_model = rf"{language_detection_model}"
|
||||||
rf"{language_detection_model}"
|
|
||||||
)
|
|
||||||
self.model = fasttext.load_model(language_detection_model)
|
self.model = fasttext.load_model(language_detection_model)
|
||||||
|
|
||||||
def predict_lang(self, text):
|
def predict_lang(self, text):
|
||||||
predictions = self.model.predict(text, k=5) # returns top 2 matching languages
|
predictions = self.model.predict(text, k=3) # returns top 2 matching languages
|
||||||
language_codes = []
|
language_codes = []
|
||||||
for prediction in predictions[0]:
|
for prediction in predictions[0]:
|
||||||
language_codes.append(prediction.replace("__label__", ""))
|
language_codes.append(prediction.replace("__label__", ""))
|
||||||
|
|
||||||
return language_codes
|
return language_codes
|
||||||
|
|
||||||
|
|
||||||
class STT:
|
class STT:
|
||||||
samplerate = None
|
samplerate = None
|
||||||
args = ""
|
args = ""
|
||||||
|
|
@ -92,9 +102,7 @@ class STT:
|
||||||
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
|
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.model = Model(
|
self.model = Model(rf"{vosk_model}")
|
||||||
rf"{vosk_model}"
|
|
||||||
)
|
|
||||||
self.dump_fn = None
|
self.dump_fn = None
|
||||||
|
|
||||||
self.q = gevent.queue.Queue()
|
self.q = gevent.queue.Queue()
|
||||||
|
|
@ -132,8 +140,9 @@ class STT:
|
||||||
def stop_recognition(self):
|
def stop_recognition(self):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
|
loadSettings()
|
||||||
speech_recognition_service = STT()
|
if settings["STT"]["USE_STT"] and settings["STT"]["LANGUAGE"] != '':
|
||||||
|
speech_recognition_service = STT()
|
||||||
|
|
||||||
|
|
||||||
class TTS:
|
class TTS:
|
||||||
|
|
@ -151,16 +160,16 @@ class TTS:
|
||||||
break
|
break
|
||||||
self.engine.setProperty("voice", matching_id)
|
self.engine.setProperty("voice", matching_id)
|
||||||
|
|
||||||
|
settings_folder = os.path.dirname(settingsPath)
|
||||||
if environment == "dev":
|
if environment == "dev":
|
||||||
settings_folder = os.path.dirname(settingsPath)
|
|
||||||
src_folder = os.path.dirname(settings_folder)
|
src_folder = os.path.dirname(settings_folder)
|
||||||
|
bot_folder = os.path.dirname(src_folder)
|
||||||
saveLocation = os.path.join(
|
saveLocation = os.path.join(
|
||||||
src_folder, "sounds\\tts", f"Internal_{count}.mp3"
|
bot_folder, "sounds", f"Internal_{count}.mp3"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
resources_folder = os.path.dirname(settingsPath)
|
|
||||||
saveLocation = os.path.join(
|
saveLocation = os.path.join(
|
||||||
resources_folder, "sounds\\tts", f"Internal_{count}.mp3"
|
settings_folder, "sounds", f"Internal_{count}.mp3"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.engine.save_to_file(message, saveLocation)
|
self.engine.save_to_file(message, saveLocation)
|
||||||
|
|
@ -175,11 +184,13 @@ class TTS:
|
||||||
|
|
||||||
return [voice.name for voice in voices]
|
return [voice.name for voice in voices]
|
||||||
|
|
||||||
|
loadSettings()
|
||||||
text_to_speech_service = TTS()
|
if settings["TTS"]["USE_TTS"]:
|
||||||
|
text_to_speech_service = TTS()
|
||||||
|
|
||||||
# endpoints
|
# endpoints
|
||||||
|
|
||||||
|
|
||||||
@app.route("/stream", methods=["GET"])
|
@app.route("/stream", methods=["GET"])
|
||||||
def stream_recognition():
|
def stream_recognition():
|
||||||
def generate():
|
def generate():
|
||||||
|
|
@ -194,14 +205,6 @@ def stop_recording():
|
||||||
return Response("Speech recognition stopped", status=200)
|
return Response("Speech recognition stopped", status=200)
|
||||||
|
|
||||||
|
|
||||||
# @app.before_request
|
|
||||||
# def custom_warning():
|
|
||||||
# if environment == "dev":
|
|
||||||
# print(
|
|
||||||
# # "Running in internal development environment. This server is not for production use."
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/terminate", methods=["GET"])
|
@app.route("/terminate", methods=["GET"])
|
||||||
def terminate_processes():
|
def terminate_processes():
|
||||||
shutdown_server()
|
shutdown_server()
|
||||||
|
|
@ -215,35 +218,55 @@ def shutdown_server():
|
||||||
func()
|
func()
|
||||||
|
|
||||||
|
|
||||||
# @app.route("/detect", methods=["POST"])
|
|
||||||
# def server_status():
|
|
||||||
# try:
|
|
||||||
# request_data = request.json
|
|
||||||
# message = request_data.get("message", "")
|
|
||||||
# confidence_values = detector.compute_language_confidence_values(message)
|
|
||||||
# for language, value in confidence_values:
|
|
||||||
# print(f"{language.name}: {value:.2f}")
|
|
||||||
# message = request_data.get("message", "")
|
|
||||||
# except Exception as e:
|
|
||||||
# return jsonify({"error": "An error occurred"}), 500
|
|
||||||
# return jsonify({"message": "Audio triggered"}), 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/status", methods=["GET"])
|
@app.route("/status", methods=["GET"])
|
||||||
def server_status():
|
def server_status():
|
||||||
return jsonify({"status": "server is running"})
|
return jsonify({"status": "server is running"})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/detect", methods=["POST"])
|
||||||
|
def get_language():
|
||||||
|
try:
|
||||||
|
request_data = request.json
|
||||||
|
message = request_data.get("message", "")
|
||||||
|
lang = LanguageDetection().predict_lang(message)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": "An error occurred"}), 500
|
||||||
|
return jsonify({"languages": lang}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/translate", methods=["POST"])
|
||||||
|
def get_translation():
|
||||||
|
loadSettings()
|
||||||
|
request_data = request.json
|
||||||
|
message = request_data.get("message", "")
|
||||||
|
detectedLanguage = request_data.get("language", "")
|
||||||
|
try:
|
||||||
|
# try:
|
||||||
|
translated = MyMemoryTranslator(
|
||||||
|
source=detectedLanguage, target=settings["LANGUAGE"]["TRANSLATE_TO"]
|
||||||
|
).translate(message)
|
||||||
|
# except Exception as e:
|
||||||
|
# return jsonify({"error": str(e), "code":429 }), 429
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e), "code":500 }), 500
|
||||||
|
return jsonify({"translation": translated}), 200
|
||||||
|
|
||||||
|
|
||||||
@app.route("/audio", methods=["POST"])
|
@app.route("/audio", methods=["POST"])
|
||||||
def trigger_backend_event():
|
def trigger_backend_event():
|
||||||
try:
|
try:
|
||||||
request_data = request.json
|
request_data = request.json
|
||||||
message = request_data.get("message", "")
|
message = request_data.get("message", "")
|
||||||
|
filteredMessage = re.sub(
|
||||||
|
r"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)",
|
||||||
|
"a link",
|
||||||
|
message,
|
||||||
|
)
|
||||||
voice = request_data.get("voice")
|
voice = request_data.get("voice")
|
||||||
count = request_data.get("count")
|
count = request_data.get("count")
|
||||||
text_to_speech_service.say(message, voice, count)
|
text_to_speech_service.say(filteredMessage, voice, count)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": "An error occurred"}), 500
|
return jsonify({"error": e}), 500
|
||||||
return jsonify({"message": "Audio triggered"}), 200
|
return jsonify({"message": "Audio triggered"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -253,25 +276,16 @@ def get_voices():
|
||||||
voices = text_to_speech_service.voices()
|
voices = text_to_speech_service.voices()
|
||||||
return jsonify({"voices": voices}), 200
|
return jsonify({"voices": voices}), 200
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": "An error occurred"}), 500
|
return jsonify({"error": e}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
LANGUAGE = LanguageDetection()
|
|
||||||
lang = LANGUAGE.predict_lang("hola cómo estás")
|
|
||||||
print(lang)
|
|
||||||
text = "Keep it up. You are awesome"
|
|
||||||
translated = MyMemoryTranslator(
|
|
||||||
source="english", target="spanish latin america"
|
|
||||||
).translate(text)
|
|
||||||
print(translated)
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
settings.read(settingsPath)
|
loadSettings()
|
||||||
port = int(settings["GENERAL"]["PORT"])
|
port = int(settings["GENERAL"]["PORT"])
|
||||||
else:
|
else:
|
||||||
environment = "dev"
|
environment = "dev"
|
||||||
port = 9000
|
port = 9000
|
||||||
stream_recognition()
|
stream_recognition()
|
||||||
|
|
||||||
app.run(host="127.0.0.1", port=port)
|
serve(app, host="0.0.0.0", port=port)
|
||||||
app.terminate()
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
packagerConfig: {
|
|
||||||
icon: './src/images/icon.ico',
|
|
||||||
asar: true,
|
|
||||||
extraResource: ['./src/config/loquendo.db', './src/sounds', './backend', './language_detection_model', './speech_to_text_models'],
|
|
||||||
},
|
|
||||||
rebuildConfig: {},
|
|
||||||
makers: [
|
|
||||||
{
|
|
||||||
name: '@electron-forge/maker-squirrel',
|
|
||||||
config: {
|
|
||||||
setupIcon: './src/images/icon.ico',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '@electron-forge/maker-zip',
|
|
||||||
platforms: ['darwin'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '@electron-forge/maker-deb',
|
|
||||||
config: {
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '@electron-forge/maker-rpm',
|
|
||||||
config: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
name: '@electron-forge/plugin-auto-unpack-natives',
|
|
||||||
config: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
@ -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
|
||||||
113
package.json
|
|
@ -1,43 +1,78 @@
|
||||||
{
|
{
|
||||||
"name": "loquendo-bot",
|
"name": "loquendo-bot",
|
||||||
"version": "2.4.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": "npm run backend && 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",
|
||||||
"backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./src/backend/loquendoBot_backend.py"
|
"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"
|
||||||
"ini": "^2.0.0",
|
]
|
||||||
"kill-process-by-name": "^1.0.5",
|
},
|
||||||
"node-google-tts-api": "^1.1.1",
|
"keywords": [],
|
||||||
"querystring": "^0.2.1",
|
"author": {
|
||||||
"socket.io": "^4.7.1",
|
"name": "Khyretos",
|
||||||
"socket.io-client": "^4.7.1",
|
"email": "khyretos@gmail.com"
|
||||||
"tmi.js": "^1.8.5",
|
},
|
||||||
"url": "^0.11.1",
|
"license": "ISC",
|
||||||
"winston": "^3.10.0",
|
"dependencies": {
|
||||||
"write-ini-file": "^4.0.1"
|
"@mediapipe/tasks-vision": "^0.10.12",
|
||||||
},
|
"axios": "^1.4.0",
|
||||||
"devDependencies": {
|
"dlivetv-api": "^1.0.10",
|
||||||
"@electron-forge/cli": "^6.2.1",
|
"emoji-picker-element": "^1.21.0",
|
||||||
"@electron-forge/maker-deb": "^6.2.1",
|
"express": "^4.18.2",
|
||||||
"@electron-forge/maker-rpm": "^6.2.1",
|
"flag-icons": "^7.1.0",
|
||||||
"@electron-forge/maker-squirrel": "^6.2.1",
|
"ini": "^2.0.0",
|
||||||
"@electron-forge/maker-zip": "^6.2.1",
|
"kill-process-by-name": "^1.0.5",
|
||||||
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
|
"node-google-tts-api": "^1.1.1",
|
||||||
"electron": "^25.9.8"
|
"p5": "^1.9.2",
|
||||||
}
|
"querystring": "^0.2.1",
|
||||||
|
"socket.io": "^4.7.1",
|
||||||
|
"socket.io-client": "^4.7.1",
|
||||||
|
"sockette": "^2.0.6",
|
||||||
|
"tmi.js": "^1.8.5",
|
||||||
|
"url": "^0.11.1",
|
||||||
|
"winston": "^3.10.0",
|
||||||
|
"write-ini-file": "^4.0.1",
|
||||||
|
"youtube-chat": "^2.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-forge/cli": "^6.2.1",
|
||||||
|
"@electron-forge/maker-deb": "^6.2.1",
|
||||||
|
"@electron-forge/maker-rpm": "^6.2.1",
|
||||||
|
"@electron-forge/maker-zip": "^6.2.1",
|
||||||
|
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
|
||||||
|
"@electron-internal/eslint-config": "^1.0.1",
|
||||||
|
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||||
|
"electron": "^25.9.8",
|
||||||
|
"electron-builder": "^24.9.1",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.1.2",
|
||||||
|
"prettier": "^3.1.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
https://alphacephei.com/vosk/models
|
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.
|
||||||
|
|
|
||||||
746
src/css/chat.css
|
|
@ -1,517 +1,469 @@
|
||||||
@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;
|
||||||
font-family: 'FRAMDCN';
|
padding-right: 50px;
|
||||||
position: relative;
|
font-family: 'FRAMDCN';
|
||||||
z-index: 1;
|
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 0.5s forwards;
|
animation: floatup 0.5s forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.botText {
|
.botText {
|
||||||
color: #000;
|
color: #000;
|
||||||
font-family: Helvetica;
|
font-family: Helvetica;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.botText span {
|
.botText span {
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #e0e0e0;
|
background: #e0e0e0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
animation: floatup 0.5s forwards;
|
animation: floatup 0.5s forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes floatup {
|
@keyframes floatup {
|
||||||
from {
|
from {
|
||||||
transform: translateY(14px);
|
transform: translateY(14px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: translateY(0px);
|
transform: translateY(0px);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.full-chat-block {
|
.full-chat-block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-bar-collapsible {
|
.chat-bar-collapsible {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible {
|
.collapsible {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background-color: #4c4c6a;
|
background-color: #4c4c6a;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chatBox {
|
.chatBox {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 0 4px var(--main-color4);
|
box-shadow: 0 0 4px var(--main-color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-window {
|
.chat-window {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
max-height: calc(100% - 60px);
|
max-height: calc(100% - 60px);
|
||||||
background: #2f323b;
|
background: #2f323b;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input {
|
.chat-input {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input input {
|
.chat-input input {
|
||||||
height: 59px;
|
height: 59px;
|
||||||
line-height: 60px;
|
line-height: 60px;
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
border: none;
|
border: none;
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 60px);
|
||||||
color: var(--chat-bubble-message);
|
color: var(--chat-bubble-message);
|
||||||
text-indent: 10px;
|
text-indent: 10px;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input button {
|
.chat-input button {
|
||||||
float: right;
|
float: right;
|
||||||
outline: 0 none;
|
outline: 0 none;
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
padding: 2px 0 0 0;
|
padding: 2px 0 0 0;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input input[good] + button {
|
.chat-input input[good] + button {
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
|
box-shadow:
|
||||||
|
0 0 2px rgba(0, 0, 0, 0.12),
|
||||||
|
0 2px 4px rgba(0, 0, 0, 0.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input input[good] + button:hover {
|
.chat-input input[good] + button:hover {
|
||||||
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
box-shadow:
|
||||||
/* 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 {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
position: static;
|
position: static;
|
||||||
display: inline-block;
|
width: 100%;
|
||||||
width: 100%;
|
padding: 10px 0px 0px 0px;
|
||||||
padding: 0px 0px 10px 0px;
|
display: grid;
|
||||||
|
grid-template: 1fr / 1fr;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-container-user {
|
.msg-container > * {
|
||||||
direction: rtl;
|
grid-column: 1 / 1;
|
||||||
position: static;
|
grid-row: 1 / 1;
|
||||||
display: inline-block;
|
}
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
.msg-container.sender {
|
||||||
|
place-items: self-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-container.user {
|
||||||
|
place-items: self-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-box {
|
.msg-box {
|
||||||
background: var(--chat-bubble);
|
background: var(--chat-bubble);
|
||||||
color: white;
|
color: white;
|
||||||
min-width: 150px;
|
min-width: 100px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 20px 5px 5px 25px;
|
padding: 18px 5px 5px 5px;
|
||||||
margin: 20px 0px 0px 25px;
|
box-shadow:
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
|
0 0 2px rgba(0, 0, 0, 0.12),
|
||||||
width: fit-content;
|
0 2px 4px rgba(0, 0, 0, 0.24);
|
||||||
|
width: fit-content;
|
||||||
|
position: relative;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-box-user {
|
.msg-box.sender {
|
||||||
background: var(--chat-bubble);
|
margin: 25px 25px 0px 35px;
|
||||||
color: white;
|
}
|
||||||
text-align: -webkit-left;
|
|
||||||
min-width: 150px;
|
.msg-box.user {
|
||||||
border-radius: 5px;
|
text-align: left;
|
||||||
padding: 20px 15px 10px 5px;
|
margin: 25px 35px 0px 0px;
|
||||||
margin: 0px 35px 0px 25px;
|
|
||||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.24);
|
|
||||||
width: fit-content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-box-user-temp {
|
.msg-box-user-temp {
|
||||||
background: var(--chat-bubble-temp);
|
background: var(--chat-bubble-temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-img {
|
.user-img {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 50%;
|
position: relative;
|
||||||
height: 50px;
|
border-radius: 50%;
|
||||||
width: 50px;
|
height: 50px;
|
||||||
z-index: 5;
|
width: 50px;
|
||||||
|
z-index: 5;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-img-user {
|
.messages.user {
|
||||||
display: inline-block;
|
margin-right: 20px;
|
||||||
border-radius: 50%;
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-user {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg {
|
.msg {
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
color: var(--chat-bubble-message);
|
color: var(--chat-bubble-message);
|
||||||
margin: 0 0 0 0;
|
margin: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-temp {
|
.msg-temp {
|
||||||
color: var(--chat-bubble-message-temp);
|
color: var(--chat-bubble-message-temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .msg:first-of-type {
|
|
||||||
margin-top: 8px;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.timestamp {
|
.timestamp {
|
||||||
color: var(--chat-bubble-header);
|
color: var(--chat-bubble-header);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: 'xxii_avenmedium';
|
font-family: 'xxii_avenmedium';
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp-temp {
|
.timestamp-temp {
|
||||||
color: var(--chat-bubble-header-temp);
|
color: var(--chat-bubble-header-temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
float: left;
|
background-color: var(--main-color4);
|
||||||
background-color: var(--main-color4);
|
color: white;
|
||||||
margin-left: 25px;
|
position: relative;
|
||||||
color: white;
|
border-radius: 5px;
|
||||||
position: relative;
|
z-index: 3;
|
||||||
padding: 5px 5px 5px 30px;
|
align-self: start;
|
||||||
border-radius: 5px;
|
|
||||||
top: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.username-user {
|
.username.sender {
|
||||||
background-color: var(--main-color4);
|
padding: 0px 5px 5px 30px;
|
||||||
margin-right: 25px;
|
margin: 20px 5px 5px 25px;
|
||||||
color: white;
|
}
|
||||||
padding: 5px 40px 5px 15px;
|
|
||||||
border-radius: 5px;
|
.username.user {
|
||||||
margin: 0px 30px 5px 5px;
|
padding: 0px 30px 5px 5px;
|
||||||
top: 15px;
|
margin: 20px 30px 5px 5px;
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.username-temp {
|
.username-temp {
|
||||||
color: var(--chat-bubble-header-temp);
|
color: var(--chat-bubble-header-temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-time {
|
.post-time {
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
padding: 3px 5px 0px 15px;
|
color: white;
|
||||||
color: white;
|
display: inline-block;
|
||||||
display: inline-block;
|
background-color: var(--main-color4);
|
||||||
background-color: var(--main-color4);
|
position: relative;
|
||||||
right: 15px;
|
z-index: 2;
|
||||||
top: -19px;
|
border-radius: 5px;
|
||||||
position: relative;
|
align-self: start;
|
||||||
z-index: 2;
|
}
|
||||||
border-radius: 5px;
|
.post-time.sender {
|
||||||
text-align: center;
|
padding: 5px 5px 5px 15px;
|
||||||
|
margin: 0px 0px 0px 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-time-user {
|
.post-time.user {
|
||||||
font-size: 8pt;
|
padding: 5px 15px 5px 5px;
|
||||||
padding: 3px 15px 0px 5px;
|
margin: 0px 50px 0px 0px;
|
||||||
margin: 5px -15px 0px -10px;
|
|
||||||
color: white;
|
|
||||||
display: inline-block;
|
|
||||||
background-color: var(--main-color4);
|
|
||||||
right: 60px;
|
|
||||||
top: -19px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .msg-self .msg-box {
|
|
||||||
border-radius: 6px 6px 6px 6px;
|
|
||||||
background: var(--main-color1);
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-self .user-img {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-self .msg {
|
|
||||||
text-align: justify;
|
|
||||||
text-justify: inter-word;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.mmg {
|
.mmg {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
height: 50px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container-user {
|
|
||||||
direction: ltr;
|
|
||||||
height: 50px;
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-circle {
|
.status-circle {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
border-radius: 50%;
|
||||||
border-radius: 50%;
|
z-index: 6;
|
||||||
margin-left: -15px;
|
position: relative;
|
||||||
z-index: 6;
|
align-self: start;
|
||||||
margin-top: -30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-circle-user {
|
.status-circle.sender {
|
||||||
width: 20px;
|
margin-left: 40px;
|
||||||
height: 20px;
|
}
|
||||||
border-radius: 50%;
|
|
||||||
z-index: 6;
|
.status-circle.user {
|
||||||
margin-top: -30px;
|
margin-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-select {
|
.menu-select {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: var(--main-color3);
|
background-color: var(--main-color3);
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
outline: none;
|
outline: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='34' viewBox='0 0 24 24' width='32' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
|
background-image: url("data:image/svg+xml;utf8,<svg fill='white' height='34' viewBox='0 0 24 24' width='32' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position-x: 100%;
|
background-position-x: 100%;
|
||||||
background-position-y: 5px;
|
background-position-y: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-select {
|
.top-select {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-image {
|
.info-image {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-select option {
|
.top-select option {
|
||||||
margin: 40px;
|
margin: 40px;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
|
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
|
||||||
background-color: var(--top-bar);
|
background-color: var(--top-bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenu {
|
.AdvancedMenu {
|
||||||
border: 1px var(--main-color2) solid;
|
border: 1px var(--main-color2) solid;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
min-width: 555px;
|
min-width: 555px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendStyle {
|
.legendStyle {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
padding: 0.2em 0.8em;
|
padding: 0.2em 0.8em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenuRow {
|
.AdvancedMenuRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenuLabel {
|
.AdvancedMenuLabel {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 125px;
|
width: 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenuLabel2 {
|
.AdvancedMenuLabel2 {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenuLabel3 {
|
.AdvancedMenuLabel3 {
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#SaveAdvancedSettingsButton {
|
#SaveAdvancedSettingsButton {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle {
|
.toggle {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background-color: var(--main-color3);
|
background-color: var(--main-color3);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* After slide changes */
|
/* After slide changes */
|
||||||
|
|
||||||
.toggle:after {
|
.toggle:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--main-color2);
|
background-color: var(--main-color2);
|
||||||
left: 5px;
|
left: 5px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox checked effect */
|
/* Checkbox checked effect */
|
||||||
|
|
||||||
.checkbox:checked + .toggle::after {
|
.checkbox:checked + .toggle::after {
|
||||||
left: 25px;
|
left: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox checked toggle label bg color */
|
/* Checkbox checked toggle label bg color */
|
||||||
|
|
||||||
.checkbox:checked + .toggle {
|
.checkbox:checked + .toggle {
|
||||||
background-color: var(--main-color1);
|
background-color: var(--main-color1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox vanished */
|
/* Checkbox vanished */
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Small toggle */
|
/* Small toggle */
|
||||||
|
|
@ -519,36 +471,110 @@ h1 {
|
||||||
/* toggle in label designing */
|
/* toggle in label designing */
|
||||||
|
|
||||||
.toggle-small {
|
.toggle-small {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: var(--main-color3);
|
background-color: var(--main-color3);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* After slide changes */
|
/* After slide changes */
|
||||||
|
|
||||||
.toggle-small:after {
|
.toggle-small:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox checked effect */
|
/* Checkbox checked effect */
|
||||||
|
|
||||||
.checkbox:checked + .toggle-small::after {
|
.checkbox:checked + .toggle-small::after {
|
||||||
left: 13px;
|
left: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox checked toggle label bg color */
|
/* Checkbox checked toggle label bg color */
|
||||||
|
|
||||||
.checkbox:checked + .toggle-small {
|
.checkbox:checked + .toggle-small {
|
||||||
background-color: var(--main-color1);
|
background-color: var(--main-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotes {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotes:hover .dark {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fi {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-header {
|
||||||
|
background-color: var(--main-color4);
|
||||||
|
border-radius: 5px;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 10px 0px 5px -5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-message {
|
||||||
|
position: relative;
|
||||||
|
margin: 20px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-message.user {
|
||||||
|
margin: -20px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-icon {
|
||||||
|
position: relative;
|
||||||
|
padding: 0px 0px 0px 0px;
|
||||||
|
margin: -45px 0px 0px -40px;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-icon {
|
||||||
|
position: relative;
|
||||||
|
top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-icon {
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-icon.user {
|
||||||
|
left: -18px;
|
||||||
|
top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-flag {
|
||||||
|
left: unset;
|
||||||
|
right: 18px;
|
||||||
|
top: -65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emote {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
311
src/css/home.css
|
|
@ -1,253 +1,274 @@
|
||||||
/* Basic styling */
|
/* Basic styling */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--main-color1: #6e2c8c;
|
--main-color1: #6e2c8c;
|
||||||
--main-color1-temp: #6e2c8c;
|
--main-color1-temp: #6e2c8c;
|
||||||
/*Left bar and top right bar*/
|
/*Left bar and top right bar*/
|
||||||
--main-color2: white;
|
--main-color2: white;
|
||||||
--main-color2-temp: white;
|
--main-color2-temp: white;
|
||||||
/*Icons and text*/
|
/*Icons and text*/
|
||||||
--main-color3: #211e1e;
|
--main-color3: #211e1e;
|
||||||
--main-color3-temp: #211e1e;
|
--main-color3-temp: #211e1e;
|
||||||
/*Buttons and input*/
|
/*Buttons and input*/
|
||||||
--main-color4: #2f2c34;
|
--main-color4: #2f2c34;
|
||||||
--main-color4-temp: #2f2c34;
|
--main-color4-temp: #2f2c34;
|
||||||
--top-bar: #100b12;
|
--top-bar: #100b12;
|
||||||
--top-bar-temp: #100b12;
|
--top-bar-temp: #100b12;
|
||||||
--mid-section: #352d3d;
|
--mid-section: #352d3d;
|
||||||
--mid-section-temp: #352d3d;
|
--mid-section-temp: #352d3d;
|
||||||
--chat-bubble: #7a6d7f;
|
--chat-bubble: #7a6d7f;
|
||||||
--chat-bubble-header: #141414;
|
--chat-bubble-header: #141414;
|
||||||
--chat-bubble-username: white;
|
--chat-bubble-username: white;
|
||||||
--chat-bubble-message: white;
|
--chat-bubble-message: white;
|
||||||
--chat-bubble-temp: #7a6d7f;
|
--chat-bubble-temp: #7a6d7f;
|
||||||
--chat-bubble-header-temp: #141414;
|
--chat-bubble-header-temp: #141414;
|
||||||
--chat-bubble-message-temp: white;
|
--chat-bubble-message-temp: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*:before,
|
*:before,
|
||||||
*:after {
|
*:after {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
/* border-top-left-radius: 20px; */
|
|
||||||
/* border-top-right-radius: 20px; */
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', sans-serif;
|
height: 100%;
|
||||||
background: transparent;
|
margin: 0;
|
||||||
}
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: transparent;
|
||||||
/* Styling of window frame and titlebar */
|
position: relative;
|
||||||
|
/* overflow-y: hidden;
|
||||||
body {
|
overflow-x: hidden; */
|
||||||
/* border: 1px solid #48545c; */
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar {
|
#titlebar {
|
||||||
display: block;
|
display: block;
|
||||||
/* position: fixed; */
|
/* position: fixed; */
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: calc(100%);
|
width: calc(100%);
|
||||||
background-color: var(--top-bar);
|
background-color: var(--top-bar);
|
||||||
/* border-top-left-radius: 20px;
|
/* border-top-left-radius: 20px;
|
||||||
border-top-right-radius: 20px; */
|
border-top-right-radius: 20px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.maximized #titlebar {
|
.maximized #titlebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main {
|
#main {
|
||||||
height: calc(100% - 32px);
|
height: calc(100% - 32px);
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar {
|
#titlebar {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar #drag-region {
|
#titlebar #drag-region {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#titlebar {
|
#titlebar {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-title {
|
#window-title {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: 'Segoe UI', sans-serif;
|
font-family: 'Segoe UI', sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maximized #window-title {
|
.maximized #window-title {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-title span {
|
#window-title span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-controls {
|
#window-controls {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 46px);
|
grid-template-columns: repeat(3, 46px);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-controls .button {
|
#window-controls .button {
|
||||||
grid-row: 1 / span 1;
|
grid-row: 1 / span 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (-webkit-device-pixel-ratio: 1.5),
|
@media (-webkit-device-pixel-ratio: 1.5),
|
||||||
(device-pixel-ratio: 1.5),
|
(device-pixel-ratio: 1.5),
|
||||||
(-webkit-device-pixel-ratio: 2),
|
(-webkit-device-pixel-ratio: 2),
|
||||||
(device-pixel-ratio: 2),
|
(device-pixel-ratio: 2),
|
||||||
(-webkit-device-pixel-ratio: 3),
|
(-webkit-device-pixel-ratio: 3),
|
||||||
(device-pixel-ratio: 3) {
|
(device-pixel-ratio: 3) {
|
||||||
#window-controls .icon {
|
#window-controls .icon {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-controls .button {
|
#window-controls .button {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-controls .button:hover {
|
#window-controls .button:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
/* filter: brightness(150%); */
|
/* filter: brightness(150%); */
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-controls .button:active {
|
#window-controls .button:active {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-button:hover {
|
#close-button:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
/* border-top-right-radius: 20px; */
|
/* border-top-right-radius: 20px; */
|
||||||
background: #f1707a !important;
|
background: #f1707a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-button:active {
|
#close-button:active {
|
||||||
background: #f1707a !important;
|
background: #f1707a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-button:active .icon {
|
#close-button:active .icon {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
background: #f1707a !important;
|
background: #f1707a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#min-button {
|
#min-button {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#max-button,
|
#max-button,
|
||||||
#restore-button {
|
#restore-button {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-button {
|
#close-button {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#restore-button {
|
#restore-button {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maximized #restore-button {
|
.maximized #restore-button {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maximized #max-button {
|
.maximized #max-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-mic {
|
.active-mic {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about {
|
.about {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
text-align: -webkit-center;
|
text-align: -webkit-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-selector {
|
.language-selector {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: 'NotoColorEmojiLimited', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
font-family:
|
||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
'NotoColorEmojiLimited',
|
||||||
left: 50%;
|
-apple-system,
|
||||||
transform: translateX(-50%);
|
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, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
font-family:
|
||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
'NotoColorEmojiLimited',
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Helvetica,
|
||||||
|
Arial,
|
||||||
|
sans-serif,
|
||||||
|
'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji',
|
||||||
|
'Segoe UI Symbol';
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-item {
|
.language-item {
|
||||||
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 !important;
|
background-color: lightblue !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warn {
|
.warn {
|
||||||
background-color: #f39c12 !important;
|
background-color: #f39c12 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: #e74c3c !important;
|
background-color: #e74c3c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logTable {
|
#logTable {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#Logs {
|
#Logs {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
341
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;
|
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: 0.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: 0.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: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-circle-left {
|
.collapse-circle-left {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.circle-right {
|
.circle-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
-webkit-clip-path: polygon(100% 100%, 0 0, 100% 0);
|
-webkit-clip-path: polygon(100% 100%, 0 0, 100% 0);
|
||||||
clip-path: ellipse(68% 50% at 94% 50%);
|
clip-path: ellipse(68% 50% at 94% 50%);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
background-color: var(--main-color3);
|
background-color: var(--main-color3);
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
top: 32px;
|
top: 32px;
|
||||||
right: 199px;
|
right: 199px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-circle-right {
|
.collapse-circle-right {
|
||||||
right: 0px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-cog {
|
.fa-cog {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-eye {
|
.fa-eye {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#min-button {
|
#min-button {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#max-button,
|
#max-button,
|
||||||
#restore-button {
|
#restore-button {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close-button {
|
#close-button {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-container {
|
#mini-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-main {
|
#mini-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-top-bar {
|
#mini-top-bar {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background-color: var(--top-bar-temp);
|
background-color: var(--top-bar-temp);
|
||||||
/* border-top-left-radius: 20px;
|
/* border-top-left-radius: 20px;
|
||||||
border-top-right-radius: 20px; */
|
border-top-right-radius: 20px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-left {
|
#mini-left {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: var(--main-color1-temp);
|
background-color: var(--main-color1-temp);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-mid {
|
#mini-mid {
|
||||||
flex: 8;
|
flex: 8;
|
||||||
background-color: var(--mid-section-temp);
|
background-color: var(--mid-section-temp);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mini-right {
|
#mini-right {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
background-color: var(--main-color4-temp);
|
background-color: var(--main-color4-temp);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop-selection {
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.miniText {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: white;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pop-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--main-color3);
|
||||||
|
z-index: 1;
|
||||||
|
height: 400px;
|
||||||
|
width: max-content;
|
||||||
|
overflow: auto;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
top: -400px;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
pop-content div {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.pop-content div:hover {
|
||||||
|
backdrop-filter: invert(50%);
|
||||||
|
}
|
||||||
|
.pop:hover .pop-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-select {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-to-channel {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--main-color3);
|
||||||
|
top: calc(-50% - 3px);
|
||||||
|
border: 1px solid #444;
|
||||||
|
z-index: 1;
|
||||||
|
width: 107px;
|
||||||
|
}
|
||||||
|
|
||||||
|
send-to-channel div {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
send-to-channel div:hover {
|
||||||
|
backdrop-filter: invert(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-select:hover .send-to-channel {
|
||||||
|
display: block;
|
||||||
|
backdrop-filter: invert(50%);
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-select {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,179 +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(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress2::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress3::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track {
|
input[type='range'].styled-slider.slider-progress4::-webkit-slider-runnable-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #1a1a1a;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*mozilla*/
|
/*mozilla*/
|
||||||
input[type='range'].styled-slider::-moz-range-thumb {
|
input[type='range'].styled-slider::-moz-range-thumb {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 0 2px black;
|
box-shadow: 0 0 2px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider::-moz-range-track {
|
input[type='range'].styled-slider::-moz-range-track {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress1::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress1::-moz-range-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress2::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress2::-moz-range-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress3::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress3::-moz-range-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress4::-moz-range-track {
|
input[type='range'].styled-slider.slider-progress4::-moz-range-track {
|
||||||
background: linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat, #464646;
|
background:
|
||||||
|
linear-gradient(var(--main-color1), var(--main-color1)) 0 / var(--sx) 100% no-repeat,
|
||||||
|
#464646;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*ms*/
|
/*ms*/
|
||||||
input[type='range'].styled-slider::-ms-fill-upper {
|
input[type='range'].styled-slider::-ms-fill-upper {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider::-ms-fill-lower {
|
input[type='range'].styled-slider::-ms-fill-lower {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider::-ms-thumb {
|
input[type='range'].styled-slider::-ms-thumb {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 0 2px black;
|
box-shadow: 0 0 2px black;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider::-ms-track {
|
input[type='range'].styled-slider::-ms-track {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress1::-ms-fill-lower {
|
input[type='range'].styled-slider.slider-progress1::-ms-fill-lower {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 1em 0 0 1em;
|
border-radius: 1em 0 0 1em;
|
||||||
margin: -undefined 0 -undefined -undefined;
|
margin: -undefined 0 -undefined -undefined;
|
||||||
background: var(--main-color1);
|
background: var(--main-color1);
|
||||||
border: none;
|
border: none;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress2::-ms-fill-lower {
|
input[type='range'].styled-slider.slider-progress2::-ms-fill-lower {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 1em 0 0 1em;
|
border-radius: 1em 0 0 1em;
|
||||||
margin: -undefined 0 -undefined -undefined;
|
margin: -undefined 0 -undefined -undefined;
|
||||||
background: var(--main-color1);
|
background: var(--main-color1);
|
||||||
border: none;
|
border: none;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress3::-ms-fill-lower {
|
input[type='range'].styled-slider.slider-progress3::-ms-fill-lower {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 1em 0 0 1em;
|
border-radius: 1em 0 0 1em;
|
||||||
margin: -undefined 0 -undefined -undefined;
|
margin: -undefined 0 -undefined -undefined;
|
||||||
background: var(--main-color1);
|
background: var(--main-color1);
|
||||||
border: none;
|
border: none;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range'].styled-slider.slider-progress4::-ms-fill-lower {
|
input[type='range'].styled-slider.slider-progress4::-ms-fill-lower {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 1em 0 0 1em;
|
border-radius: 1em 0 0 1em;
|
||||||
margin: -undefined 0 -undefined -undefined;
|
margin: -undefined 0 -undefined -undefined;
|
||||||
background: var(--main-color1);
|
background: var(--main-color1);
|
||||||
border: none;
|
border: none;
|
||||||
border-right-width: 0;
|
border-right-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputBox {
|
.inputBox {
|
||||||
border: none;
|
border: none;
|
||||||
width: 38px;
|
width: 38px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
522
src/css/tabs.css
|
|
@ -1,441 +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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language {
|
.language {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#AdvancedMenu_mask {
|
#AdvancedMenu_mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background-color: var(--mid-section);
|
background-color: var(--mid-section);
|
||||||
font-family: 'xxii_avenmedium';
|
font-family: 'xxii_avenmedium';
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ThemeCreator_mask {
|
#ThemeCreator_mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background-color: var(--mid-section);
|
background-color: var(--mid-section);
|
||||||
font-family: 'xxii_avenmedium';
|
font-family: 'xxii_avenmedium';
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fname {
|
.fname {
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
border: none;
|
border: none;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='password'] {
|
input[type='password'] {
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
border: none;
|
border: none;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
/* To make space for the reveal button */
|
/* To make space for the reveal button */
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='url'] {
|
input[type='url'] {
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
border: none;
|
border: none;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
width: 260px;
|
width: 260px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='lol'] {
|
input[type='lol'] {
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
border: none;
|
border: none;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
/* To make space for the reveal button */
|
/* To make space for the reveal button */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for the reveal button */
|
/* Style for the reveal button */
|
||||||
.password-toggle-btn {
|
.password-toggle-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
left: 450px;
|
left: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the default appearance of the button */
|
/* Hide the default appearance of the button */
|
||||||
.password-toggle-btn::-moz-focus-inner {
|
.password-toggle-btn::-moz-focus-inner {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the reveal icon (you can use your preferred icon or font) */
|
/* Style the reveal icon (you can use your preferred icon or font) */
|
||||||
.password-toggle-icon {
|
.password-toggle-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#toasts {
|
#toasts {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
/* Adjust the distance from the bottom of the screen */
|
/* Adjust the distance from the bottom of the screen */
|
||||||
right: 0%;
|
right: 0%;
|
||||||
/* Center the toasts horizontally */
|
/* Center the toasts horizontally */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
animation: 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 */
|
||||||
.info {
|
.info {
|
||||||
background-color: lightblue !important;
|
background-color: lightblue !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
background-color: #2ecc71 !important;
|
background-color: #2ecc71 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
background-color: #f39c12 !important;
|
background-color: #f39c12 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: #e74c3c !important;
|
background-color: #e74c3c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS animation for the toast appearance */
|
/* CSS animation for the toast appearance */
|
||||||
@keyframes toastAnimation {
|
@keyframes toastAnimation {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS animation for the toast disappearance */
|
/* CSS animation for the toast disappearance */
|
||||||
@keyframes toastDisappear {
|
@keyframes toastDisappear {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
font-size: 17pt;
|
font-size: 17pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
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';
|
|
||||||
z-index: 999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .tooltip .tooltiptext {
|
|
||||||
width: 120px;
|
|
||||||
background-color: black;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip .tooltiptext::after {
|
|
||||||
content: "";
|
|
||||||
margin-left: -5px;
|
|
||||||
border-width: 5px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: black transparent transparent transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip:hover .tooltiptext {
|
|
||||||
visibility: visible;
|
|
||||||
} */
|
|
||||||
|
|
||||||
div[type='text']:disabled {
|
div[type='text']:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text']:disabled {
|
input[type='text']:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[type='text']:disabled {
|
button[type='text']:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type2='text']:disabled {
|
input[type2='text']:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
div:disabled {
|
div:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
filter: brightness(200%);
|
filter: brightness(200%);
|
||||||
}
|
}
|
||||||
|
|
||||||
div:disabled {
|
div:disabled {
|
||||||
background: #4b4b4b;
|
background: #4b4b4b;
|
||||||
filter: brightness(200%);
|
filter: brightness(200%);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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,161 +1,163 @@
|
||||||
#tstx {
|
#tstx {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionrow {
|
.optionrow {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.LabelText {
|
.LabelText {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
margin: 0px 0px 5px 0px;
|
margin: 0px 0px 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#volume-icon {
|
#volume-icon {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
scale: 0.75;
|
scale: 0.75;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image {
|
#image {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TTSVolume {
|
.TTSVolume {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#SoundVolume {
|
#SoundVolume {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testLabel {
|
.testLabel {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
resize: none;
|
resize: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: var(--main-color3);
|
background: var(--main-color3);
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
font-family: 'xxii_avenmedium';
|
font-family: 'xxii_avenmedium';
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SaveConfig {
|
.SaveConfig {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SmallButton {
|
.SmallButton {
|
||||||
color: var(--main-color2);
|
color: var(--main-color2);
|
||||||
width: 50px;
|
width: 50px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-shadow: 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;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AdvancedMenuIcon2 {
|
.AdvancedMenuIcon2 {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:hover {
|
input:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
select:hover {
|
select:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea:hover {
|
textarea:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
label:hover {
|
label:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.circle-right:hover {
|
.circle-right:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
.circle-left:hover {
|
.circle-left:hover {
|
||||||
filter: brightness(120%);
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,87 @@
|
||||||
.middle {
|
.middle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container {
|
.slider-container {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .bar {
|
.slider-container .bar {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: var(--main-color3);
|
background-color: var(--main-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .bar .fill {
|
.slider-container .bar .fill {
|
||||||
display: block;
|
display: block;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
background-color: var(--main-color1);
|
background-color: var(--main-color1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .slider {
|
.slider-container .slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .slider::-webkit-slider-thumb {
|
.slider-container .slider::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background-color: var(--main-color2);
|
background-color: var(--main-color2);
|
||||||
border-top-left-radius: 50%;
|
border-top-left-radius: 50%;
|
||||||
border-top-right-radius: 50%;
|
border-top-right-radius: 50%;
|
||||||
border-bottom-right-radius: 50%;
|
border-bottom-right-radius: 50%;
|
||||||
border-bottom-left-radius: 50%;
|
border-bottom-left-radius: 50%;
|
||||||
transform: rotate(-45deg) translate(0%, 0%);
|
transform: rotate(-45deg) translate(0%, 0%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
/* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */
|
/* box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); */
|
||||||
box-shadow: 0 0 3px rgb(0 0 0 / 10%);
|
box-shadow: 0 0 3px rgb(0 0 0 / 10%);
|
||||||
/* transition: .3s ease-in-out; */
|
/* transition: .3s ease-in-out; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .slider:active::-webkit-slider-thumb,
|
.slider-container .slider:active::-webkit-slider-thumb,
|
||||||
.slider-container .slider::-webkit-slider-thumb:hover {
|
.slider-container .slider::-webkit-slider-thumb:hover {
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
transform: rotate(-45deg) translate(50%, -50%);
|
transform: rotate(-45deg) translate(50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container .slider:active::-webkit-slider-thumb {
|
.slider-container .slider:active::-webkit-slider-thumb {
|
||||||
box-shadow: 0 0 0 3px rgba(255, 255, 255, 1);
|
box-shadow: 0 0 0 3px rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-icon-container {
|
.option-icon-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/* justify-content: center; */
|
/* justify-content: center; */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* width: 78px; */
|
/* width: 78px; */
|
||||||
/* float: right; */
|
/* float: right; */
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brush-icon-container {
|
.brush-icon-container {
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
font-size: 20pt;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: var(--main-color2);
|
fill: var(--main-color2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 29 KiB |
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/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 |
1592
src/index.html
188
src/js/amazon.js
|
|
@ -1,118 +1,118 @@
|
||||||
|
/* global settings, callback, addVoiceService, amazonVoices, */
|
||||||
|
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
const aws4 = require('aws4');
|
const aws4 = require('aws4');
|
||||||
|
|
||||||
function getAmazonVoices() {
|
function getAmazonVoices() {
|
||||||
if (!settings.AMAZON.USE_AMAZON) {
|
if (!settings.AMAZON.USE_AMAZON) {
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoiceService('Amazon');
|
addVoiceService('Amazon');
|
||||||
|
|
||||||
let primaryVoice = document.querySelector('#primaryAmazonVoice');
|
const primaryVoice = document.querySelector('#primaryAmazonVoice');
|
||||||
let secondaryVoice = document.querySelector('#secondaryAmazonVoice');
|
const secondaryVoice = document.querySelector('#secondaryAmazonVoice');
|
||||||
|
|
||||||
function setVoicesinSelect(voiceSelect) {
|
function setVoicesinSelect(voiceSelect) {
|
||||||
const voices = Object.values(amazonVoices);
|
const voices = Object.values(amazonVoices);
|
||||||
voices.forEach((voice) => {
|
voices.forEach(voice => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.classList.add('option');
|
option.classList.add('option');
|
||||||
|
|
||||||
option.value = voice;
|
option.value = voice;
|
||||||
option.innerHTML = voice;
|
option.innerHTML = voice;
|
||||||
|
|
||||||
voiceSelect.appendChild(option);
|
voiceSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setVoicesinSelect(primaryVoice);
|
setVoicesinSelect(primaryVoice);
|
||||||
primaryVoice.value = settings.AMAZON.PRIMARY_VOICE;
|
primaryVoice.value = settings.AMAZON.PRIMARY_VOICE;
|
||||||
setVoicesinSelect(secondaryVoice);
|
setVoicesinSelect(secondaryVoice);
|
||||||
secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE;
|
secondaryVoice.value = settings.AMAZON.SECONDARY_VOICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.AMAZON.USE_AMAZON) {
|
if (settings.AMAZON.USE_AMAZON) {
|
||||||
getAmazonVoices();
|
getAmazonVoices();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PollyTTS {
|
class PollyTTS {
|
||||||
constructor() {}
|
textToSpeech(options, callback) {
|
||||||
|
if (!options) {
|
||||||
|
return callback(new Error('Options are missing'));
|
||||||
|
}
|
||||||
|
|
||||||
textToSpeech(options, callback) {
|
const qs = {
|
||||||
if (!options) {
|
Text: options.text,
|
||||||
return callback(new Error('Options are missing'));
|
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}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const qs = {
|
let body = '';
|
||||||
Text: options.text,
|
res.on('readable', () => {
|
||||||
TextType: options.textType || 'text',
|
body += res.read();
|
||||||
VoiceId: options.voiceId || 'Mia',
|
});
|
||||||
SampleRate: options.sampleRate || 22050,
|
res.on('end', () => {
|
||||||
OutputFormat: options.outputFormat || 'mp3',
|
callback(null, body);
|
||||||
Engine: options.engine || 'neural',
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const opts = {
|
return undefined;
|
||||||
service: 'polly',
|
})
|
||||||
region: options.region || 'us-east-1',
|
.on('error', e => {
|
||||||
path: `/v1/speech?${querystring.stringify(qs)}`,
|
callback(e);
|
||||||
signQuery: true,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
// you can also pass AWS credentials in explicitly (otherwise taken from process.env)
|
return null;
|
||||||
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();
|
const pollyTTS = new PollyTTS();
|
||||||
|
|
|
||||||
211
src/js/auth.js
|
|
@ -1,103 +1,130 @@
|
||||||
|
/* global settings,twitch,trovo, fs, settingsPath, ini, shell, options, axios */
|
||||||
|
|
||||||
const twitchAuthentication = () =>
|
const twitchAuthentication = () =>
|
||||||
new Promise((resolve) => {
|
new Promise(resolve => {
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const redirectUri = 'http://localhost:1989/auth';
|
const redirectUri = 'http://localhost:1989/auth';
|
||||||
const scopes = ['chat:edit', 'chat:read'];
|
const scopes = ['chat:edit', 'chat:read', 'user:read:follows', 'user:read:subscriptions', 'channel:read:redemptions'];
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
let tempAuthServer = express();
|
const tempAuthServer = express();
|
||||||
const port = 1989;
|
const port = 1989;
|
||||||
|
|
||||||
const { parse: parseQueryString } = require('querystring');
|
const { parse: parseQueryString } = require('querystring');
|
||||||
|
|
||||||
tempAuthServer.use(function (req, res, next) {
|
tempAuthServer.use(function (req, res, next) {
|
||||||
if (req.url !== '/auth') {
|
console.log('1');
|
||||||
let token = parseQueryString(req.query.auth);
|
if (req.url !== '/auth') {
|
||||||
settings.TWITCH.OAUTH_TOKEN = token['#access_token'];
|
console.log(req.url);
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
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']);
|
resolve(token['#access_token']);
|
||||||
stopServer();
|
stopServer();
|
||||||
}
|
} else {
|
||||||
next();
|
res.send(htmlString);
|
||||||
});
|
}
|
||||||
|
|
||||||
function stopServer() {
|
|
||||||
tempAuthServer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlString = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Authentication</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Authentication successful! You can close this window now.</h1>
|
|
||||||
<form name="auth" "action="auth" method="get" >
|
|
||||||
<input type="text" id="auth" name="auth"/>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<script>
|
|
||||||
function onSubmitComplete() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
document.querySelector("#auth").style.display="none";
|
|
||||||
document.querySelector("#auth").value = document.location.hash;
|
|
||||||
document.auth.submit();
|
|
||||||
setTimeout(onSubmitComplete, 500);
|
|
||||||
</script>
|
|
||||||
`;
|
|
||||||
|
|
||||||
tempAuthServer.get('/auth', (req, res) => {
|
|
||||||
res.send(htmlString);
|
|
||||||
});
|
|
||||||
|
|
||||||
tempAuthServer.post('/auth', (req, res) => {
|
|
||||||
res.render('authentication', { name: req.body.name });
|
|
||||||
});
|
|
||||||
|
|
||||||
const server = http.createServer(tempAuthServer);
|
|
||||||
|
|
||||||
server.listen(port, () => {
|
|
||||||
const authURL = `https://id.twitch.tv/oauth2/authorize?client_id=${settings.TWITCH.CLIENT_ID}&redirect_uri=${encodeURIComponent(
|
|
||||||
redirectUri,
|
|
||||||
)}&response_type=token&scope=${scopes.join(' ')}`;
|
|
||||||
shell.openExternal(authURL);
|
|
||||||
});
|
|
||||||
|
|
||||||
function stopServer() {
|
|
||||||
server.close(() => {});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTwitchUserId() {
|
const htmlString = `
|
||||||
// Get user Logo with access token
|
<!DOCTYPE html>
|
||||||
options = {
|
<html>
|
||||||
method: 'GET',
|
<head>
|
||||||
url: `https://api.twitch.tv/helix/users`,
|
<title>Authentication</title>
|
||||||
headers: { 'Client-ID': settings.TWITCH.CLIENT_ID, Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}` },
|
</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(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
axios
|
|
||||||
.request(options)
|
|
||||||
.then((responseLogoUrl) => {
|
|
||||||
console.log(responseLogoUrl.data.data[0]);
|
|
||||||
settings.TWITCH.USERNAME = responseLogoUrl.data.data[0].display_name;
|
|
||||||
settings.TWITCH.USER_LOGO_URL = responseLogoUrl.data.data[0].profile_image_url;
|
|
||||||
settings.TWITCH.USER_ID = responseLogoUrl.data.data[0].id;
|
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function getTwitchOauthToken() {
|
function getTwitchOauthToken() {
|
||||||
return twitchAuthentication().then((res) => {
|
return twitchAuthentication().then(res => {
|
||||||
getTwitchUserId();
|
twitch.getTwitchUserId();
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getTwitchOauthToken };
|
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 };
|
||||||
|
|
|
||||||
|
|
@ -1,167 +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 spawn = require('child_process').spawn;
|
||||||
var kill = require('kill-process-by-name');
|
const kill = require('kill-process-by-name');
|
||||||
let python;
|
let python;
|
||||||
|
|
||||||
async function getInstalledVoices() {
|
async function getInstalledVoices() {
|
||||||
if (!settings.TTS.USE_TTS) {
|
if (!settings.TTS.USE_TTS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addVoiceService('Internal');
|
addVoiceService('Internal');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' });
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/voices`, { method: 'GET' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('Response:', responseData);
|
console.log('Voices:', responseData);
|
||||||
internalVoices = 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 {
|
} else {
|
||||||
console.error('Failed to send termination signal to Flask server.');
|
message.message = 'Error, Could not translate message';
|
||||||
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
|
return getTranslatedMessage(message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Error sending termination signal:', error);
|
if (responseData.code === 429) {
|
||||||
}
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
|
setTranslatedMessage({
|
||||||
let primaryVoice = document.querySelector('#primaryVoice');
|
originalMessage: message.message,
|
||||||
let secondaryVoice = document.querySelector('#secondaryVoice');
|
translation: 'Rate limit exceeded, please change translation service.',
|
||||||
|
messageId: message.messageId,
|
||||||
function setVoicesinSelect(voiceSelect) {
|
language: message.language,
|
||||||
const voices = Object.values(internalVoices.voices);
|
formattedMessage: message.formattedMessage,
|
||||||
voices.forEach((voice) => {
|
username: message.username,
|
||||||
const option = document.createElement('option');
|
logoUrl: message.logoUrl
|
||||||
option.classList.add('option');
|
|
||||||
|
|
||||||
option.value = voice;
|
|
||||||
option.innerHTML = voice;
|
|
||||||
|
|
||||||
voiceSelect.appendChild(option);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setVoicesinSelect(primaryVoice);
|
} catch (error) {
|
||||||
primaryVoice.value = settings.TTS.PRIMARY_VOICE;
|
console.error('Error sending termination signal:', error);
|
||||||
setVoicesinSelect(secondaryVoice);
|
message.language.detectedLanguage = getLanguageProperties('en-GB');
|
||||||
secondaryVoice.value = settings.TTS.SECONDARY_VOICE;
|
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() {
|
async function getBackendServerStatus() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' });
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/status`, { method: 'GET' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('Response:', responseData);
|
console.log('Status:', responseData);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to send termination signal to Flask server.');
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending termination signal:', error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startSTT() {
|
function startSTT() {
|
||||||
const eventSource = new EventSource('http://127.0.0.1:9000/stream');
|
const eventSource = new EventSource('http://127.0.0.1:9000/stream');
|
||||||
|
|
||||||
eventSource.addEventListener('message', (event) => {
|
eventSource.addEventListener('message', event => {
|
||||||
const result = event.data;
|
const result = event.data;
|
||||||
console.log(result); // Log the received data
|
console.log(result); // Log the received data
|
||||||
});
|
});
|
||||||
|
|
||||||
eventSource.addEventListener('error', (event) => {
|
eventSource.addEventListener('error', event => {
|
||||||
console.error('EventSource failed:', event);
|
console.error('EventSource failed:', event);
|
||||||
|
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getInternalTTSAudio(requestData) {
|
async function getInternalTTSAudio(requestData) {
|
||||||
ttsRequestCount++;
|
ttsRequestCount++;
|
||||||
requestData.count = ttsRequestCount;
|
requestData.count = ttsRequestCount;
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
method: 'POST', // HTTP method
|
method: 'POST', // HTTP method
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json', // Specify the content type
|
'Content-Type': 'application/json' // Specify the content type
|
||||||
},
|
},
|
||||||
body: JSON.stringify(requestData), // Convert the data to JSON and include it in the request body
|
body: JSON.stringify(requestData) // Convert the data to JSON and include it in the request body
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions);
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/audio`, requestOptions);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('Response:', responseData);
|
console.log('Audio:', responseData);
|
||||||
return ttsRequestCount;
|
return ttsRequestCount;
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to send termination signal to Flask server.');
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending termination signal:', error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createBackendServer = () =>
|
const createBackendServer = () =>
|
||||||
new Promise((resolve) => {
|
new Promise(resolve => {
|
||||||
if (main.isPackaged) {
|
if (main.isPackaged) {
|
||||||
python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']);
|
python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']);
|
||||||
} else {
|
} else {
|
||||||
python = spawn('python', ['-u', path.join(pythonPath, './loquendoBot_backend.py'), settingsPath, 'dev']);
|
python = spawn('python', ['-u', path.join(resourcesPath, '../backend/loquendoBot_backend.py'), settingsPath, 'dev']);
|
||||||
}
|
}
|
||||||
// Capture the stdout of the Python process
|
// Capture the stdout of the Python process
|
||||||
python.stdout.on('data', (data) => {
|
python.stdout.on('data', data => {
|
||||||
console.info(`${data}`);
|
console.info(`${data}`);
|
||||||
});
|
|
||||||
|
|
||||||
// Capture the stderr of the Python process
|
|
||||||
python.stderr.on('data', (data) => {
|
|
||||||
console.error(`${data}`);
|
|
||||||
resolve('finished'); // cannot get it to resolve with stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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() {
|
// Capture the stderr of the Python process
|
||||||
try {
|
python.stderr.on('data', data => {
|
||||||
createBackendServer().then(() => {
|
// console.error(`${data}`);
|
||||||
getBackendServerStatus();
|
if (data.toString().startsWith('INFO:waitress:Serving on')) {
|
||||||
getInstalledVoices();
|
resolve('finished');
|
||||||
if (settings.STT.USE_STT) {
|
} else {
|
||||||
startSTT();
|
console.error(`${data}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.error('Error during backend initialization:', error);
|
// 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();
|
initiateBackend();
|
||||||
|
|
||||||
//TODO: convert to restartServer function
|
// TODO: convert to restartServer function
|
||||||
ipcRenderer.on('quit-event', async () => {
|
ipcRenderer.on('quit-event', async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' });
|
const response = await fetch(`http://127.0.0.1:${settings.GENERAL.PORT}/terminate`, { method: 'GET' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
console.log('Response:', responseData);
|
console.log('Response:', responseData);
|
||||||
kill('loquendoBot_backend');
|
kill('loquendoBot_backend');
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to send termination signal to Flask server.');
|
console.error('Failed to send termination signal to Flask server.');
|
||||||
kill('loquendoBot_backend');
|
kill('loquendoBot_backend');
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending termination signal:', error);
|
|
||||||
kill('loquendoBot_backend');
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending termination signal:', error);
|
||||||
|
kill('loquendoBot_backend');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { getInternalTTSAudio };
|
module.exports = { getInternalTTSAudio, getDetectedLanguage, getTranslatedMessage };
|
||||||
|
|
|
||||||
221
src/js/chat.js
|
|
@ -1,55 +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-user';
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
article.innerHTML = messageTemplates.userTemplate;
|
messageId++;
|
||||||
|
|
||||||
const userImg = article.querySelector('.icon-container-user > .user-img-user');
|
// Create chat message from received data
|
||||||
if (userImg) {
|
const article = document.createElement('article');
|
||||||
userImg.src = settings.TWITCH.USER_LOGO_URL;
|
article.setAttribute('id', messageId);
|
||||||
}
|
article.className = 'msg-container user';
|
||||||
|
|
||||||
const postTime = article.querySelector('.post-time-user');
|
article.innerHTML = messageTemplates.userTemplate;
|
||||||
|
|
||||||
const iconContainer = article.querySelector('.icon-container-user');
|
const userImg = article.querySelector('.user-img');
|
||||||
iconContainer.appendChild(postTime);
|
if (userImg) {
|
||||||
|
userImg.src = settings.TWITCH.USER_LOGO_URL;
|
||||||
|
}
|
||||||
|
|
||||||
if (postTime) {
|
const postTime = article.querySelector('.post-time');
|
||||||
postTime.innerText = getPostTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg = article.querySelector('.msg-box-user');
|
if (postTime) {
|
||||||
if (msg) {
|
postTime.innerText = getPostTime();
|
||||||
msg.innerText = userText;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the message to the main chat box (shows the message)
|
article.appendChild(postTime);
|
||||||
showChatMessage(article, true);
|
|
||||||
|
|
||||||
twitch.sendMessage(userText);
|
const msg = article.querySelector('.msg-box');
|
||||||
|
if (msg) {
|
||||||
|
await replaceChatMessageWithCustomEmojis(userText).then(data => {
|
||||||
|
msg.innerHTML = data;
|
||||||
|
|
||||||
// Empty input box after sending message
|
// Appends the message to the main chat box (shows the message)
|
||||||
document.body.querySelector('#textInput').value = '';
|
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
|
||||||
|
|
@ -58,21 +97,21 @@ document.body.querySelector('#SendButton').addEventListener('click', () => {
|
||||||
|
|
||||||
// Left panel
|
// Left panel
|
||||||
document.body.querySelector('.circle-left').addEventListener('click', () => {
|
document.body.querySelector('.circle-left').addEventListener('click', () => {
|
||||||
const menu = document.body.querySelector('.sidepanel-left');
|
const menu = document.body.querySelector('.sidepanel-left');
|
||||||
|
|
||||||
if (menu.classList.contains('collapse-menu-left')) {
|
if (menu.classList.contains('collapse-menu-left')) {
|
||||||
menu.classList.remove('collapse-menu-left');
|
menu.classList.remove('collapse-menu-left');
|
||||||
} else {
|
} else {
|
||||||
menu.classList.add('collapse-menu-left');
|
menu.classList.add('collapse-menu-left');
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftCircle = document.body.querySelector('.circle-left');
|
const leftCircle = document.body.querySelector('.circle-left');
|
||||||
|
|
||||||
if (leftCircle.classList.contains('collapse-circle-left')) {
|
if (leftCircle.classList.contains('collapse-circle-left')) {
|
||||||
leftCircle.classList.remove('collapse-circle-left');
|
leftCircle.classList.remove('collapse-circle-left');
|
||||||
} else {
|
} else {
|
||||||
leftCircle.classList.add('collapse-circle-left');
|
leftCircle.classList.add('collapse-circle-left');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// #region Show panels
|
// #region Show panels
|
||||||
|
|
@ -81,65 +120,67 @@ document.body.querySelector('.circle-left').addEventListener('click', () => {
|
||||||
// TODO : optimize show panels
|
// TODO : optimize show panels
|
||||||
// Function that shows and hides the option panels. (TTS, Configuration, Commands)
|
// Function that shows and hides the option panels. (TTS, Configuration, Commands)
|
||||||
const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
|
const displayPanel = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
|
||||||
const btn = document.querySelector(btnSelectorID);
|
const btn = document.querySelector(btnSelectorID);
|
||||||
const panel = document.querySelector(panelSelectorID);
|
const panel = document.querySelector(panelSelectorID);
|
||||||
const panels = document.querySelectorAll(panelSelectorClass);
|
const panels = document.querySelectorAll(panelSelectorClass);
|
||||||
|
|
||||||
btn.addEventListener(
|
btn.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
(event) => {
|
event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
panels.forEach((el) => {
|
panels.forEach(el => {
|
||||||
if (el === panel) return;
|
if (el === panel) return;
|
||||||
el.classList.remove('show');
|
el.classList.remove('show');
|
||||||
});
|
});
|
||||||
if (panel.classList.contains('show')) {
|
if (!panel.classList.contains('show')) {
|
||||||
} else {
|
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', '#BrowsersourcePNGTuber', '#btnBrowsersourcePNGTuber');
|
||||||
|
displayPanel('.OptionPanel', '#FaceMask', '#btnFaceMask');
|
||||||
displayPanel('.OptionPanel', '#Chat', '#btnChat');
|
displayPanel('.OptionPanel', '#Chat', '#btnChat');
|
||||||
displayPanel('.OptionPanel', '#ThemeCreator', '#btnThemeCreator');
|
displayPanel('.OptionPanel', '#ThemeCreator', '#btnThemeCreator');
|
||||||
displayPanel('.OptionPanel', '#ChatCreator', '#btnChatCreator');
|
displayPanel('.OptionPanel', '#ChatCreator', '#btnChatCreator');
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
|
const displayPanelX = (panelSelectorClass, panelSelectorID, btnSelectorID) => {
|
||||||
const btn = document.querySelector(btnSelectorID);
|
const btn = document.querySelector(btnSelectorID);
|
||||||
const panel = document.querySelector(panelSelectorID);
|
const panel = document.querySelector(panelSelectorID);
|
||||||
const panels = document.querySelectorAll(panelSelectorClass);
|
const panels = document.querySelectorAll(panelSelectorClass);
|
||||||
|
|
||||||
btn.addEventListener(
|
btn.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
(event) => {
|
event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
panels.forEach((el) => {
|
panels.forEach(el => {
|
||||||
if (el === panel) return;
|
if (el === panel) return;
|
||||||
el.classList.remove('item-active');
|
el.classList.remove('item-active');
|
||||||
});
|
});
|
||||||
if (panel.classList.contains('item-active')) {
|
if (!panel.classList.contains('item-active')) {
|
||||||
} else {
|
panel.classList.add('item-active');
|
||||||
panel.classList.add('item-active');
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
capture: true
|
||||||
capture: true,
|
}
|
||||||
},
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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', '#btnThemeCreator', '#btnThemeCreator');
|
||||||
|
|
@ -148,3 +189,7 @@ displayPanelX('.item', '#btnChatCreator', '#btnChatCreator');
|
||||||
// #region Show/Hide Theme Creator
|
// #region Show/Hide Theme Creator
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
replaceChatMessageWithCustomEmojis
|
||||||
|
};
|
||||||
|
|
|
||||||
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,33 @@
|
||||||
|
/* global settings, addVoiceService, googleVoices */
|
||||||
|
|
||||||
function getGoogleVoices() {
|
function getGoogleVoices() {
|
||||||
if (!settings.GOOGLE.USE_GOOGLE) {
|
if (!settings.GOOGLE.USE_GOOGLE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoiceService('Google');
|
addVoiceService('Google');
|
||||||
|
|
||||||
let primaryVoice = document.querySelector('#primaryGoogleVoice');
|
const primaryVoice = document.querySelector('#primaryGoogleVoice');
|
||||||
let secondaryVoice = document.querySelector('#secondaryGoogleVoice');
|
const secondaryVoice = document.querySelector('#secondaryGoogleVoice');
|
||||||
|
|
||||||
function setVoicesinSelect(voiceSelect) {
|
function setVoicesinSelect(voiceSelect) {
|
||||||
const voices = Object.values(googleVoices);
|
const voices = Object.values(googleVoices);
|
||||||
voices.forEach((voice) => {
|
voices.forEach(voice => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.classList.add('option');
|
option.classList.add('option');
|
||||||
|
|
||||||
option.value = voice;
|
option.value = voice;
|
||||||
option.innerHTML = voice;
|
option.innerHTML = voice;
|
||||||
|
|
||||||
voiceSelect.appendChild(option);
|
voiceSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setVoicesinSelect(primaryVoice);
|
setVoicesinSelect(primaryVoice);
|
||||||
primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE;
|
primaryVoice.value = settings.GOOGLE.PRIMARY_VOICE;
|
||||||
setVoicesinSelect(secondaryVoice);
|
setVoicesinSelect(secondaryVoice);
|
||||||
secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE;
|
secondaryVoice.value = settings.GOOGLE.SECONDARY_VOICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.GOOGLE.USE_GOOGLE) {
|
if (settings.GOOGLE.USE_GOOGLE) {
|
||||||
getGoogleVoices();
|
getGoogleVoices();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,331 +1,334 @@
|
||||||
// TODO: Enable STT:
|
// TODO: Enable STT:
|
||||||
// Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns)
|
// Output STT to TTS? *TTS service selection* (for now, later add the option to choose a specific voice with mega dropdowns)
|
||||||
// *automatic translation: make an translation.js and add ALL the texts and have it translated if user chooses a language in top bar
|
// *automatic translation: make an translation.js and add ALL the texts and have it translated if user chooses a language in top bar
|
||||||
// *info page with credits, version and more info
|
// *info page with credits, version and more info
|
||||||
|
|
||||||
const languages = {
|
const languages = {
|
||||||
acehnese: { IETF: 'ace-ID', 'ISO-639': 'ace' },
|
none: { IETF: 'none', ISO639: 'none', ISO3166: 'xx' },
|
||||||
afrikaans: { IETF: 'af-ZA', 'ISO-639': 'af' },
|
english: { IETF: 'en-GB', ISO639: 'en', ISO3166: 'gb' },
|
||||||
akan: { IETF: 'ak-GH', 'ISO-639': 'ak' },
|
spanish: { IETF: 'es-ES', ISO639: 'es', ISO3166: 'es' },
|
||||||
albanian: { IETF: 'sq-AL', 'ISO-639': 'sq' },
|
dutch: { IETF: 'nl-NL', ISO639: 'nl', ISO3166: 'nl' },
|
||||||
amharic: { IETF: 'am-ET', 'ISO-639': 'am' },
|
'chinese simplified': { IETF: 'zh-CN', ISO639: 'zh', ISO3166: 'cn' },
|
||||||
'antigua and barbuda creole english': { IETF: 'aig-AG', 'ISO-639': 'aig' },
|
russian: { IETF: 'ru-RU', ISO639: 'ru', ISO3166: 'ru' },
|
||||||
arabic: { IETF: 'ar-SA', 'ISO-639': 'ar' },
|
indonesian: { IETF: 'id-ID', ISO639: 'id', ISO3166: 'id' },
|
||||||
'arabic egyptian': { IETF: 'ar-EG', 'ISO-639': 'ar' },
|
hindi: { IETF: 'hi-IN', ISO639: 'hi', ISO3166: 'in' },
|
||||||
aragonese: { IETF: 'an-ES', 'ISO-639': 'an' },
|
filipino: { IETF: 'fil-PH', ISO639: 'fil', ISO3166: 'ph' },
|
||||||
armenian: { IETF: 'hy-AM', 'ISO-639': 'hy' },
|
turkish: { IETF: 'tr-TR', ISO639: 'tr', ISO3166: 'tr' },
|
||||||
assamese: { IETF: 'as-IN', 'ISO-639': 'as' },
|
acehnese: { IETF: 'ace-ID', ISO639: 'ace', ISO3166: 'id' },
|
||||||
asturian: { IETF: 'ast-ES', 'ISO-639': 'ast' },
|
afrikaans: { IETF: 'af-ZA', ISO639: 'af', ISO3166: 'za' },
|
||||||
'austrian german': { IETF: 'de-AT', 'ISO-639': 'de' },
|
akan: { IETF: 'ak-GH', ISO639: 'ak', ISO3166: 'gh' },
|
||||||
awadhi: { IETF: 'awa-IN', 'ISO-639': 'awa' },
|
albanian: { IETF: 'sq-AL', ISO639: 'sq', ISO3166: 'al' },
|
||||||
'ayacucho quechua': { IETF: 'quy-PE', 'ISO-639': 'quy' },
|
amharic: { IETF: 'am-ET', ISO639: 'am', ISO3166: 'et' },
|
||||||
azerbaijani: { IETF: 'az-AZ', 'ISO-639': 'az' },
|
'antigua and barbuda creole english': { IETF: 'aig-AG', ISO639: 'aig', ISO3166: 'ag' },
|
||||||
'bahamas creole english': { IETF: 'bah-BS', 'ISO-639': 'bah' },
|
arabic: { IETF: 'ar-SA', ISO639: 'ar', ISO3166: 'sa' },
|
||||||
bajan: { IETF: 'bjs-BB', 'ISO-639': 'bjs' },
|
'arabic egyptian': { IETF: 'ar-EG', ISO639: 'arz', ISO3166: 'eg' },
|
||||||
balinese: { IETF: 'ban-ID', 'ISO-639': 'ban' },
|
aragonese: { IETF: 'es-ES', ISO639: 'an', ISO3166: 'es' },
|
||||||
'balkan gipsy': { IETF: 'rm-RO', 'ISO-639': 'rm' },
|
armenian: { IETF: 'hy-AM', ISO639: 'hy', ISO3166: 'am' },
|
||||||
bambara: { IETF: 'bm-ML', 'ISO-639': 'bm' },
|
assamese: { IETF: 'as-IN', ISO639: 'as', ISO3166: 'in' },
|
||||||
banjar: { IETF: 'bjn-ID', 'ISO-639': 'bjn' },
|
asturian: { IETF: 'ast-ES', ISO639: 'ast', ISO3166: 'es' },
|
||||||
bashkir: { IETF: 'ba-RU', 'ISO-639': 'ba' },
|
'austrian german': { IETF: 'de-AT', ISO639: 'de', ISO3166: 'at' },
|
||||||
basque: { IETF: 'eu-ES', 'ISO-639': 'eu' },
|
awadhi: { IETF: 'awa-IN', ISO639: 'awa', ISO3166: 'in' },
|
||||||
belarusian: { IETF: 'be-BY', 'ISO-639': 'be' },
|
'ayacucho quechua': { IETF: 'quy-PE', ISO639: 'quy', ISO3166: 'pe' },
|
||||||
'belgian french': { IETF: 'fr-BE', 'ISO-639': 'fr' },
|
azerbaijani: { IETF: 'az-AZ', ISO639: 'az', ISO3166: 'az' },
|
||||||
bemba: { IETF: 'bem-ZM', 'ISO-639': 'bem' },
|
'bahamas creole english': { IETF: 'bah-BS', ISO639: 'bah', ISO3166: 'bs' },
|
||||||
bengali: { IETF: 'bn-IN', 'ISO-639': 'bn' },
|
bajan: { IETF: 'bjs-BB', ISO639: 'bjs', ISO3166: 'bb' },
|
||||||
bhojpuri: { IETF: 'bho-IN', 'ISO-639': 'bho' },
|
balinese: { IETF: 'ban-ID', ISO639: 'ban', ISO3166: 'id' },
|
||||||
bihari: { IETF: 'bh-IN', 'ISO-639': 'bh' },
|
'balkan gipsy': { IETF: 'rm-RO', ISO639: 'rm', ISO3166: 'ro' },
|
||||||
bislama: { IETF: 'bi-VU', 'ISO-639': 'bi' },
|
bambara: { IETF: 'bm-ML', ISO639: 'bm', ISO3166: 'ml' },
|
||||||
borana: { IETF: 'gax-KE', 'ISO-639': 'gax' },
|
banjar: { IETF: 'bjn-ID', ISO639: 'bjn', ISO3166: 'id' },
|
||||||
bosnian: { IETF: 'bs-BA', 'ISO-639': 'bs' },
|
bashkir: { IETF: 'ba-RU', ISO639: 'ba', ISO3166: 'ru' },
|
||||||
'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', 'ISO-639': 'bs' },
|
basque: { IETF: 'eu-ES', ISO639: 'eu', ISO3166: 'es-pv' },
|
||||||
breton: { IETF: 'br-FR', 'ISO-639': 'br' },
|
belarusian: { IETF: 'be-BY', ISO639: 'be', ISO3166: 'by' },
|
||||||
buginese: { IETF: 'bug-ID', 'ISO-639': 'bug' },
|
'belgian french': { IETF: 'fr-BE', ISO639: 'fr', ISO3166: 'be' },
|
||||||
bulgarian: { IETF: 'bg-BG', 'ISO-639': 'bg' },
|
bemba: { IETF: 'bem-ZM', ISO639: 'bem', ISO3166: 'zm' },
|
||||||
burmese: { IETF: 'my-MM', 'ISO-639': 'my' },
|
bengali: { IETF: 'bn-IN', ISO639: 'bn', ISO3166: 'bd' },
|
||||||
catalan: { IETF: 'ca-ES', 'ISO-639': 'ca' },
|
bhojpuri: { IETF: 'bho-IN', ISO639: 'bho', ISO3166: 'in' },
|
||||||
'catalan valencian': { IETF: 'cav-ES', 'ISO-639': 'cav' },
|
bihari: { IETF: 'bh-IN', ISO639: 'bh', ISO3166: 'in' },
|
||||||
cebuano: { IETF: 'ceb-PH', 'ISO-639': 'ceb' },
|
bislama: { IETF: 'bi-VU', ISO639: 'bi', ISO3166: 'vu' },
|
||||||
'central atlas tamazight': { IETF: 'tzm-MA', 'ISO-639': 'tzm' },
|
borana: { IETF: 'gax-KE', ISO639: 'gax', ISO3166: 'ke' },
|
||||||
'central aymara': { IETF: 'ayr-BO', 'ISO-639': 'ayr' },
|
bosnian: { IETF: 'bs-BA', ISO639: 'bs', ISO3166: 'ba' },
|
||||||
'central kanuri (latin script)': { IETF: 'knc-NG', 'ISO-639': 'knc' },
|
'bosnian (cyrillic)': { IETF: 'bs-Cyrl-BA', ISO639: 'bs', ISO3166: 'ba' },
|
||||||
'chadian arabic': { IETF: 'shu-TD', 'ISO-639': 'shu' },
|
breton: { IETF: 'br-FR', ISO639: 'br', ISO3166: 'fr' },
|
||||||
chamorro: { IETF: 'ch-GU', 'ISO-639': 'ch' },
|
buginese: { IETF: 'bug-ID', ISO639: 'bug', ISO3166: 'id' },
|
||||||
cherokee: { IETF: 'chr-US', 'ISO-639': 'chr' },
|
bulgarian: { IETF: 'bg-BG', ISO639: 'bg', ISO3166: 'bg' },
|
||||||
chhattisgarhi: { IETF: 'hne-IN', 'ISO-639': 'hne' },
|
burmese: { IETF: 'my-MM', ISO639: 'my', ISO3166: 'mm' },
|
||||||
'chinese simplified': { IETF: 'zh-CN', 'ISO-639': 'zh' },
|
catalan: { IETF: 'ca-ES', ISO639: 'ca', ISO3166: 'es' },
|
||||||
'chinese trad. (hong kong)': { IETF: 'zh-HK', 'ISO-639': 'zh' },
|
'catalan valencian': { IETF: 'cav-ES', ISO639: 'cav', ISO3166: 'es' },
|
||||||
'chinese traditional': { IETF: 'zh-TW', 'ISO-639': 'zh' },
|
cebuano: { IETF: 'ceb-PH', ISO639: 'ceb', ISO3166: 'ph' },
|
||||||
'chinese traditional macau': { IETF: 'zh-MO', 'ISO-639': 'zh' },
|
'central atlas tamazight': { IETF: 'tzm-MA', ISO639: 'tzm', ISO3166: 'ma' },
|
||||||
chittagonian: { IETF: 'ctg-BD', 'ISO-639': 'ctg' },
|
'central aymara': { IETF: 'ayr-BO', ISO639: 'ayr', ISO3166: 'bo' },
|
||||||
chokwe: { IETF: 'cjk-AO', 'ISO-639': 'cjk' },
|
'central kanuri (latin script)': { IETF: 'knc-NG', ISO639: 'knc', ISO3166: 'ng' },
|
||||||
'classical greek': { IETF: 'grc-GR', 'ISO-639': 'grc' },
|
'chadian arabic': { IETF: 'shu-TD', ISO639: 'shu', ISO3166: 'td' },
|
||||||
'comorian ngazidja': { IETF: 'zdj-KM', 'ISO-639': 'zdj' },
|
chamorro: { IETF: 'ch-GU', ISO639: 'ch', ISO3166: 'gu' },
|
||||||
coptic: { IETF: 'cop-EG', 'ISO-639': 'cop' },
|
cherokee: { IETF: 'chr-US', ISO639: 'chr', ISO3166: 'us' },
|
||||||
'crimean tatar': { IETF: 'crh-RU', 'ISO-639': 'crh' },
|
chhattisgarhi: { IETF: 'hne-IN', ISO639: 'hne', ISO3166: 'in' },
|
||||||
'crioulo upper guinea': { IETF: 'pov-GW', 'ISO-639': 'pov' },
|
'chinese trad. (hong kong)': { IETF: 'zh-HK', ISO639: 'zh', ISO3166: 'hk' },
|
||||||
croatian: { IETF: 'hr-HR', 'ISO-639': 'hr' },
|
'chinese traditional': { IETF: 'zh-TW', ISO639: 'zh', ISO3166: 'tw' },
|
||||||
czech: { IETF: 'cs-CZ', 'ISO-639': 'cs' },
|
'chinese traditional macau': { IETF: 'zh-MO', ISO639: 'zh', ISO3166: 'mo' },
|
||||||
danish: { IETF: 'da-DK', 'ISO-639': 'da' },
|
chittagonian: { IETF: 'ctg-BD', ISO639: 'ctg', ISO3166: 'bd' },
|
||||||
dari: { IETF: 'prs-AF', 'ISO-639': 'prs' },
|
chokwe: { IETF: 'cjk-AO', ISO639: 'cjk', ISO3166: 'ao' },
|
||||||
dimli: { IETF: 'diq-TR', 'ISO-639': 'diq' },
|
'classical greek': { IETF: 'grc-GR', ISO639: 'grc', ISO3166: 'gr' },
|
||||||
dutch: { IETF: 'nl-NL', 'ISO-639': 'nl' },
|
'comorian ngazidja': { IETF: 'zdj-KM', ISO639: 'zdj', ISO3166: 'km' },
|
||||||
dyula: { IETF: 'dyu-CI', 'ISO-639': 'dyu' },
|
coptic: { IETF: 'cop-EG', ISO639: 'cop', ISO3166: 'eg' },
|
||||||
dzongkha: { IETF: 'dz-BT', 'ISO-639': 'dz' },
|
'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' },
|
||||||
'eastern yiddish': { IETF: 'ydd-US', 'ISO-639': 'ydd' },
|
'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' },
|
||||||
emakhuwa: { IETF: 'vmw-MZ', 'ISO-639': 'vmw' },
|
croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' },
|
||||||
english: { IETF: 'en-GB', 'ISO-639': 'en' },
|
'serbo-croatian': { IETF: 'sr-Cyrl-RS', ISO639: 'sh', ISO3166: 'sr' },
|
||||||
'english australia': { IETF: 'en-AU', 'ISO-639': 'en' },
|
czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' },
|
||||||
'english canada': { IETF: 'en-CA', 'ISO-639': 'en' },
|
danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' },
|
||||||
'english india': { IETF: 'en-IN', 'ISO-639': 'en' },
|
dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' },
|
||||||
'english ireland': { IETF: 'en-IE', 'ISO-639': 'en' },
|
dimli: { IETF: 'diq-TR', ISO639: 'diq', ISO3166: 'tr' },
|
||||||
'english new zealand': { IETF: 'en-NZ', 'ISO-639': 'en' },
|
dyula: { IETF: 'dyu-CI', ISO639: 'dyu', ISO3166: 'ci' },
|
||||||
'english singapore': { IETF: 'en-SG', 'ISO-639': 'en' },
|
dzongkha: { IETF: 'dz-BT', ISO639: 'dz', ISO3166: 'bt' },
|
||||||
'english south africa': { IETF: 'en-ZA', 'ISO-639': 'en' },
|
'eastern yiddish': { IETF: 'ydd-US', ISO639: 'ydd', ISO3166: 'il' },
|
||||||
'english us': { IETF: 'en-US', 'ISO-639': 'en' },
|
emakhuwa: { IETF: 'vmw-MZ', ISO639: 'vmw', ISO3166: 'mz' },
|
||||||
esperanto: { IETF: 'eo-EU', 'ISO-639': 'eo' },
|
'english australia': { IETF: 'en-AU', ISO639: 'en', ISO3166: 'au' },
|
||||||
estonian: { IETF: 'et-EE', 'ISO-639': 'et' },
|
'english canada': { IETF: 'en-CA', ISO639: 'en', ISO3166: 'ca' },
|
||||||
ewe: { IETF: 'ee-GH', 'ISO-639': 'ee' },
|
'english india': { IETF: 'en-IN', ISO639: 'en', ISO3166: 'in' },
|
||||||
fanagalo: { IETF: 'fn-FNG', 'ISO-639': 'fn' },
|
'english ireland': { IETF: 'en-IE', ISO639: 'en', ISO3166: 'ie' },
|
||||||
faroese: { IETF: 'fo-FO', 'ISO-639': 'fo' },
|
'english new zealand': { IETF: 'en-NZ', ISO639: 'en', ISO3166: 'nz' },
|
||||||
fijian: { IETF: 'fj-FJ', 'ISO-639': 'fj' },
|
'english singapore': { IETF: 'en-SG', ISO639: 'en', ISO3166: 'sg' },
|
||||||
filipino: { IETF: 'fil-PH', 'ISO-639': 'fil' },
|
'english south africa': { IETF: 'en-ZA', ISO639: 'en', ISO3166: 'za' },
|
||||||
finnish: { IETF: 'fi-FI', 'ISO-639': 'fi' },
|
'english us': { IETF: 'en-US', ISO639: 'en', ISO3166: 'us' },
|
||||||
flemish: { IETF: 'nl-BE', 'ISO-639': 'nl' },
|
esperanto: { IETF: 'eo-EU', ISO639: 'eo', ISO3166: 'eu' },
|
||||||
fon: { IETF: 'fon-BJ', 'ISO-639': 'fon' },
|
estonian: { IETF: 'et-EE', ISO639: 'et', ISO3166: 'ee' },
|
||||||
french: { IETF: 'fr-FR', 'ISO-639': 'fr' },
|
ewe: { IETF: 'ee-GH', ISO639: 'ee', ISO3166: 'gh' },
|
||||||
'french canada': { IETF: 'fr-CA', 'ISO-639': 'fr' },
|
fanagalo: { IETF: 'fn-FNG', ISO639: 'fn', ISO3166: 'za' },
|
||||||
'french swiss': { IETF: 'fr-CH', 'ISO-639': 'fr' },
|
faroese: { IETF: 'fo-FO', ISO639: 'fo', ISO3166: 'fo' },
|
||||||
friulian: { IETF: 'fur-IT', 'ISO-639': 'fur' },
|
fijian: { IETF: 'fj-FJ', ISO639: 'fj', ISO3166: 'fj' },
|
||||||
fula: { IETF: 'ff-FUL', 'ISO-639': 'ff' },
|
finnish: { IETF: 'fi-FI', ISO639: 'fi', ISO3166: 'fi' },
|
||||||
galician: { IETF: 'gl-ES', 'ISO-639': 'gl' },
|
flemish: { IETF: 'nl-BE', ISO639: 'nl', ISO3166: 'be' },
|
||||||
gamargu: { IETF: 'mfi-NG', 'ISO-639': 'mfi' },
|
fon: { IETF: 'fon-BJ', ISO639: 'fon', ISO3166: 'bj' },
|
||||||
garo: { IETF: 'grt-IN', 'ISO-639': 'grt' },
|
french: { IETF: 'fr-FR', ISO639: 'fr', ISO3166: 'fr' },
|
||||||
georgian: { IETF: 'ka-GE', 'ISO-639': 'ka' },
|
'french canada': { IETF: 'fr-CA', ISO639: 'fr', ISO3166: 'ca' },
|
||||||
german: { IETF: 'de-DE', 'ISO-639': 'de' },
|
'french swiss': { IETF: 'fr-CH', ISO639: 'fr', ISO3166: 'ch' },
|
||||||
gilbertese: { IETF: 'gil-KI', 'ISO-639': 'gil' },
|
friulian: { IETF: 'fur-IT', ISO639: 'fur', ISO3166: 'it' },
|
||||||
glavda: { IETF: 'glw-NG', 'ISO-639': 'glw' },
|
fula: { IETF: 'ff-FUL', ISO639: 'ff', ISO3166: 'cm' },
|
||||||
greek: { IETF: 'el-GR', 'ISO-639': 'el' },
|
galician: { IETF: 'gl-ES', ISO639: 'gl', ISO3166: 'es-ga' },
|
||||||
'grenadian creole english': { IETF: 'gcl-GD', 'ISO-639': 'gcl' },
|
gamargu: { IETF: 'mfi-NG', ISO639: 'mfi', ISO3166: 'ng' },
|
||||||
guarani: { IETF: 'gn-PY', 'ISO-639': 'gn' },
|
garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' },
|
||||||
gujarati: { IETF: 'gu-IN', 'ISO-639': 'gu' },
|
georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' },
|
||||||
'guyanese creole english': { IETF: 'gyn-GY', 'ISO-639': 'gyn' },
|
german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' },
|
||||||
'haitian creole french': { IETF: 'ht-HT', 'ISO-639': 'ht' },
|
'Low German': { IETF: 'nl-NL', ISO639: 'nds', ISO3166: 'nl' },
|
||||||
'halh mongolian': { IETF: 'khk-MN', 'ISO-639': 'khk' },
|
gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' },
|
||||||
hausa: { IETF: 'ha-NE', 'ISO-639': 'ha' },
|
glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' },
|
||||||
hawaiian: { IETF: 'haw-US', 'ISO-639': 'haw' },
|
greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' },
|
||||||
hebrew: { IETF: 'he-IL', 'ISO-639': 'he' },
|
'grenadian creole english': { IETF: 'gcl-GD', ISO639: 'gcl', ISO3166: 'gd' },
|
||||||
higi: { IETF: 'hig-NG', 'ISO-639': 'hig' },
|
guarani: { IETF: 'gn-PY', ISO639: 'gn', ISO3166: 'py' },
|
||||||
hiligaynon: { IETF: 'hil-PH', 'ISO-639': 'hil' },
|
gujarati: { IETF: 'gu-IN', ISO639: 'gu', ISO3166: 'in' },
|
||||||
'hill mari': { IETF: 'mrj-RU', 'ISO-639': 'mrj' },
|
'guyanese creole english': { IETF: 'gyn-GY', ISO639: 'gyn', ISO3166: 'gy' },
|
||||||
hindi: { IETF: 'hi-IN', 'ISO-639': 'hi' },
|
'haitian creole french': { IETF: 'ht-HT', ISO639: 'ht', ISO3166: 'ht' },
|
||||||
hmong: { IETF: 'hmn-CN', 'ISO-639': 'hmn' },
|
'halh mongolian': { IETF: 'khk-MN', ISO639: 'khk', ISO3166: 'mn' },
|
||||||
hungarian: { IETF: 'hu-HU', 'ISO-639': 'hu' },
|
hausa: { IETF: 'ha-NE', ISO639: 'ha', ISO3166: 'ne' },
|
||||||
icelandic: { IETF: 'is-IS', 'ISO-639': 'is' },
|
hawaiian: { IETF: 'haw-US', ISO639: 'haw', ISO3166: 'xx' },
|
||||||
'igbo ibo': { IETF: 'ibo-NG', 'ISO-639': 'ibo' },
|
hebrew: { IETF: 'he-IL', ISO639: 'he', ISO3166: 'il' },
|
||||||
'igbo ig': { IETF: 'ig-NG', 'ISO-639': 'ig' },
|
higi: { IETF: 'hig-NG', ISO639: 'hig', ISO3166: 'ng' },
|
||||||
ilocano: { IETF: 'ilo-PH', 'ISO-639': 'ilo' },
|
hiligaynon: { IETF: 'hil-PH', ISO639: 'hil', ISO3166: 'ph' },
|
||||||
indonesian: { IETF: 'id-ID', 'ISO-639': 'id' },
|
'hill mari': { IETF: 'mrj-RU', ISO639: 'mrj', ISO3166: 'xx' },
|
||||||
'inuktitut greenlandic': { IETF: 'kl-GL', 'ISO-639': 'kl' },
|
hmong: { IETF: 'hmn-CN', ISO639: 'hmn', ISO3166: 'cn' },
|
||||||
'irish gaelic': { IETF: 'ga-IE', 'ISO-639': 'ga' },
|
hungarian: { IETF: 'hu-HU', ISO639: 'hu', ISO3166: 'hu' },
|
||||||
italian: { IETF: 'it-IT', 'ISO-639': 'it' },
|
icelandic: { IETF: 'is-IS', ISO639: 'is', ISO3166: 'is' },
|
||||||
'italian swiss': { IETF: 'it-CH', 'ISO-639': 'it' },
|
'igbo ibo': { IETF: 'ibo-NG', ISO639: 'ibo', ISO3166: 'ng' },
|
||||||
'jamaican creole english': { IETF: 'jam-JM', 'ISO-639': 'jam' },
|
'igbo ig': { IETF: 'ig-NG', ISO639: 'ig', ISO3166: 'ng' },
|
||||||
japanese: { IETF: 'ja-JP', 'ISO-639': 'ja' },
|
ilocano: { IETF: 'ilo-PH', ISO639: 'ilo', ISO3166: 'ph' },
|
||||||
javanese: { IETF: 'jv-ID', 'ISO-639': 'jv' },
|
'inuktitut greenlandic': { IETF: 'kl-GL', ISO639: 'kl', ISO3166: 'gl' },
|
||||||
jingpho: { IETF: 'kac-MM', 'ISO-639': 'kac' },
|
'irish gaelic': { IETF: 'ga-IE', ISO639: 'ga', ISO3166: 'ie' },
|
||||||
"k'iche'": { IETF: 'quc-GT', 'ISO-639': 'quc' },
|
italian: { IETF: 'it-IT', ISO639: 'it', ISO3166: 'it' },
|
||||||
'kabiy<69>': { IETF: 'kbp-TG', 'ISO-639': 'kbp' },
|
'italian swiss': { IETF: 'it-CH', ISO639: 'it', ISO3166: 'ch' },
|
||||||
kabuverdianu: { IETF: 'kea-CV', 'ISO-639': 'kea' },
|
'jamaican creole english': { IETF: 'jam-JM', ISO639: 'jam', ISO3166: 'jm' },
|
||||||
kabylian: { IETF: 'kab-DZ', 'ISO-639': 'kab' },
|
japanese: { IETF: 'ja-JP', ISO639: 'ja', ISO3166: 'jp' },
|
||||||
kalenjin: { IETF: 'kln-KE', 'ISO-639': 'kln' },
|
javanese: { IETF: 'jv-ID', ISO639: 'jv', ISO3166: 'id' },
|
||||||
kamba: { IETF: 'kam-KE', 'ISO-639': 'kam' },
|
jingpho: { IETF: 'kac-MM', ISO639: 'kac', ISO3166: 'mm' },
|
||||||
kannada: { IETF: 'kn-IN', 'ISO-639': 'kn' },
|
"k'iche'": { IETF: 'quc-GT', ISO639: 'quc', ISO3166: 'gt' },
|
||||||
kanuri: { IETF: 'kr-KAU', 'ISO-639': 'kr' },
|
kabiye: { IETF: 'kbp-TG', ISO639: 'kbp', ISO3166: 'tg' },
|
||||||
karen: { IETF: 'kar-MM', 'ISO-639': 'kar' },
|
kabuverdianu: { IETF: 'kea-CV', ISO639: 'kea', ISO3166: 'cv' },
|
||||||
'kashmiri (devanagari script)': { IETF: 'ks-IN', 'ISO-639': 'ks' },
|
kabylian: { IETF: 'kab-DZ', ISO639: 'kab', ISO3166: 'dz' },
|
||||||
'kashmiri (arabic script)': { IETF: 'kas-IN', 'ISO-639': 'kas' },
|
kalenjin: { IETF: 'kln-KE', ISO639: 'kln', ISO3166: 'ke' },
|
||||||
kazakh: { IETF: 'kk-KZ', 'ISO-639': 'kk' },
|
kamba: { IETF: 'kam-KE', ISO639: 'kam', ISO3166: 'ke' },
|
||||||
khasi: { IETF: 'kha-IN', 'ISO-639': 'kha' },
|
kannada: { IETF: 'kn-IN', ISO639: 'kn', ISO3166: 'in' },
|
||||||
khmer: { IETF: 'km-KH', 'ISO-639': 'km' },
|
kanuri: { IETF: 'kr-KAU', ISO639: 'kr', ISO3166: 'xx' },
|
||||||
'kikuyu kik': { IETF: 'kik-KE', 'ISO-639': 'kik' },
|
karen: { IETF: 'kar-MM', ISO639: 'kar', ISO3166: 'mm' },
|
||||||
'kikuyu ki': { IETF: 'ki-KE', 'ISO-639': 'ki' },
|
'kashmiri (devanagari script)': { IETF: 'ks-IN', ISO639: 'ks', ISO3166: 'in' },
|
||||||
kimbundu: { IETF: 'kmb-AO', 'ISO-639': 'kmb' },
|
'kashmiri (arabic script)': { IETF: 'kas-IN', ISO639: 'kas', ISO3166: 'in' },
|
||||||
kinyarwanda: { IETF: 'rw-RW', 'ISO-639': 'rw' },
|
kazakh: { IETF: 'kk-KZ', ISO639: 'kk', ISO3166: 'kz' },
|
||||||
kirundi: { IETF: 'rn-BI', 'ISO-639': 'rn' },
|
khasi: { IETF: 'kha-IN', ISO639: 'kha', ISO3166: 'in' },
|
||||||
kisii: { IETF: 'guz-KE', 'ISO-639': 'guz' },
|
khmer: { IETF: 'km-KH', ISO639: 'km', ISO3166: 'kh' },
|
||||||
kongo: { IETF: 'kg-CG', 'ISO-639': 'kg' },
|
'kikuyu kik': { IETF: 'kik-KE', ISO639: 'kik', ISO3166: 'ke' },
|
||||||
konkani: { IETF: 'kok-IN', 'ISO-639': 'kok' },
|
'kikuyu ki': { IETF: 'ki-KE', ISO639: 'ki', ISO3166: 'ke' },
|
||||||
korean: { IETF: 'ko-KR', 'ISO-639': 'ko' },
|
kimbundu: { IETF: 'kmb-AO', ISO639: 'kmb', ISO3166: 'ao' },
|
||||||
'northern kurdish': { IETF: 'kmr-TR', 'ISO-639': 'kmr' },
|
kinyarwanda: { IETF: 'rw-RW', ISO639: 'rw', ISO3166: 'rw' },
|
||||||
'kurdish sorani': { IETF: 'ckb-IQ', 'ISO-639': 'ckb' },
|
kirundi: { IETF: 'rn-BI', ISO639: 'rn', ISO3166: 'bi' },
|
||||||
kyrgyz: { IETF: 'ky-KG', 'ISO-639': 'ky' },
|
kisii: { IETF: 'guz-KE', ISO639: 'guz', ISO3166: 'ke' },
|
||||||
lao: { IETF: 'lo-LA', 'ISO-639': 'lo' },
|
kongo: { IETF: 'kg-CG', ISO639: 'kg', ISO3166: 'cg' },
|
||||||
latgalian: { IETF: 'ltg-LV', 'ISO-639': 'ltg' },
|
konkani: { IETF: 'kok-IN', ISO639: 'kok', ISO3166: 'in' },
|
||||||
latin: { IETF: 'la-XN', 'ISO-639': 'la' },
|
korean: { IETF: 'ko-KR', ISO639: 'ko', ISO3166: 'kr' },
|
||||||
latvian: { IETF: 'lv-LV', 'ISO-639': 'lv' },
|
'northern kurdish': { IETF: 'kmr-TR', ISO639: 'kmr', ISO3166: 'tr' },
|
||||||
ligurian: { IETF: 'lij-IT', 'ISO-639': 'lij' },
|
'kurdish sorani': { IETF: 'ckb-IQ', ISO639: 'ckb', ISO3166: 'iq' },
|
||||||
limburgish: { IETF: 'li-NL', 'ISO-639': 'li' },
|
kyrgyz: { IETF: 'ky-KG', ISO639: 'ky', ISO3166: 'kg' },
|
||||||
lingala: { IETF: 'ln-LIN', 'ISO-639': 'ln' },
|
lao: { IETF: 'lo-LA', ISO639: 'lo', ISO3166: 'la' },
|
||||||
lithuanian: { IETF: 'lt-LT', 'ISO-639': 'lt' },
|
latgalian: { IETF: 'ltg-LV', ISO639: 'ltg', ISO3166: 'lv' },
|
||||||
lombard: { IETF: 'lmo-IT', 'ISO-639': 'lmo' },
|
latin: { IETF: 'la-XN', ISO639: 'la', ISO3166: 'xx' },
|
||||||
'luba-kasai': { IETF: 'lua-CD', 'ISO-639': 'lua' },
|
latvian: { IETF: 'lv-LV', ISO639: 'lv', ISO3166: 'lg' },
|
||||||
luganda: { IETF: 'lg-UG', 'ISO-639': 'lg' },
|
ligurian: { IETF: 'lij-IT', ISO639: 'lij', ISO3166: 'it' },
|
||||||
luhya: { IETF: 'luy-KE', 'ISO-639': 'luy' },
|
limburgish: { IETF: 'li-NL', ISO639: 'li', ISO3166: 'nl' },
|
||||||
luo: { IETF: 'luo-KE', 'ISO-639': 'luo' },
|
lingala: { IETF: 'ln-LIN', ISO639: 'ln', ISO3166: 'cd' },
|
||||||
luxembourgish: { IETF: 'lb-LU', 'ISO-639': 'lb' },
|
lithuanian: { IETF: 'lt-LT', ISO639: 'lt', ISO3166: 'lt' },
|
||||||
maa: { IETF: 'mas-KE', 'ISO-639': 'mas' },
|
lombard: { IETF: 'lmo-IT', ISO639: 'lmo', ISO3166: 'it' },
|
||||||
macedonian: { IETF: 'mk-MK', 'ISO-639': 'mk' },
|
'luba-kasai': { IETF: 'lua-CD', ISO639: 'lua', ISO3166: 'cd' },
|
||||||
magahi: { IETF: 'mag-IN', 'ISO-639': 'mag' },
|
luganda: { IETF: 'lg-UG', ISO639: 'lg', ISO3166: 'ug' },
|
||||||
maithili: { IETF: 'mai-IN', 'ISO-639': 'mai' },
|
luhya: { IETF: 'luy-KE', ISO639: 'luy', ISO3166: 'ke' },
|
||||||
malagasy: { IETF: 'mg-MG', 'ISO-639': 'mg' },
|
luo: { IETF: 'luo-KE', ISO639: 'luo', ISO3166: 'ke' },
|
||||||
malay: { IETF: 'ms-MY', 'ISO-639': 'ms' },
|
luxembourgish: { IETF: 'lb-LU', ISO639: 'lb', ISO3166: 'lu' },
|
||||||
malayalam: { IETF: 'ml-IN', 'ISO-639': 'ml' },
|
maa: { IETF: 'mas-KE', ISO639: 'mas', ISO3166: 'ke' },
|
||||||
maldivian: { IETF: 'dv-MV', 'ISO-639': 'dv' },
|
macedonian: { IETF: 'mk-MK', ISO639: 'mk', ISO3166: 'mk' },
|
||||||
maltese: { IETF: 'mt-MT', 'ISO-639': 'mt' },
|
magahi: { IETF: 'mag-IN', ISO639: 'mag', ISO3166: 'in' },
|
||||||
mandara: { IETF: 'mfi-CM', 'ISO-639': 'mfi' },
|
maithili: { IETF: 'mai-IN', ISO639: 'mai', ISO3166: 'in' },
|
||||||
manipuri: { IETF: 'mni-IN', 'ISO-639': 'mni' },
|
malagasy: { IETF: 'mg-MG', ISO639: 'mg', ISO3166: 'mg' },
|
||||||
'manx gaelic': { IETF: 'gv-IM', 'ISO-639': 'gv' },
|
malay: { IETF: 'ms-MY', ISO639: 'ms', ISO3166: 'my' },
|
||||||
maori: { IETF: 'mi-NZ', 'ISO-639': 'mi' },
|
malayalam: { IETF: 'ml-IN', ISO639: 'ml', ISO3166: 'in' },
|
||||||
marathi: { IETF: 'mr-IN', 'ISO-639': 'mr' },
|
maldivian: { IETF: 'dv-MV', ISO639: 'dv', ISO3166: 'mv' },
|
||||||
margi: { IETF: 'mrt-NG', 'ISO-639': 'mrt' },
|
maltese: { IETF: 'mt-MT', ISO639: 'mt', ISO3166: 'mt' },
|
||||||
mari: { IETF: 'mhr-RU', 'ISO-639': 'mhr' },
|
mandara: { IETF: 'mfi-CM', ISO639: 'mfi', ISO3166: 'cm' },
|
||||||
marshallese: { IETF: 'mh-MH', 'ISO-639': 'mh' },
|
manipuri: { IETF: 'mni-IN', ISO639: 'mni', ISO3166: 'in' },
|
||||||
mende: { IETF: 'men-SL', 'ISO-639': 'men' },
|
'manx gaelic': { IETF: 'gv-IM', ISO639: 'gv', ISO3166: 'im' },
|
||||||
meru: { IETF: 'mer-KE', 'ISO-639': 'mer' },
|
maori: { IETF: 'mi-NZ', ISO639: 'mi', ISO3166: 'nz' },
|
||||||
mijikenda: { IETF: 'nyf-KE', 'ISO-639': 'nyf' },
|
marathi: { IETF: 'mr-IN', ISO639: 'mr', ISO3166: 'in' },
|
||||||
minangkabau: { IETF: 'min-ID', 'ISO-639': 'min' },
|
margi: { IETF: 'mrt-NG', ISO639: 'mrt', ISO3166: 'ng' },
|
||||||
mizo: { IETF: 'lus-IN', 'ISO-639': 'lus' },
|
mari: { IETF: 'mhr-RU', ISO639: 'mhr', ISO3166: 'xx' },
|
||||||
mongolian: { IETF: 'mn-MN', 'ISO-639': 'mn' },
|
marshallese: { IETF: 'mh-MH', ISO639: 'mh', ISO3166: 'mh' },
|
||||||
montenegrin: { IETF: 'sr-ME', 'ISO-639': 'sr' },
|
mende: { IETF: 'men-SL', ISO639: 'men', ISO3166: 'sl' },
|
||||||
morisyen: { IETF: 'mfe-MU', 'ISO-639': 'mfe' },
|
meru: { IETF: 'mer-KE', ISO639: 'mer', ISO3166: 'ke' },
|
||||||
'moroccan arabic': { IETF: 'ar-MA', 'ISO-639': 'ar' },
|
mijikenda: { IETF: 'nyf-KE', ISO639: 'nyf', ISO3166: 'ke' },
|
||||||
mossi: { IETF: 'mos-BF', 'ISO-639': 'mos' },
|
minangkabau: { IETF: 'min-ID', ISO639: 'min', ISO3166: 'id' },
|
||||||
ndau: { IETF: 'ndc-MZ', 'ISO-639': 'ndc' },
|
mizo: { IETF: 'lus-IN', ISO639: 'lus', ISO3166: 'in' },
|
||||||
ndebele: { IETF: 'nr-ZA', 'ISO-639': 'nr' },
|
mongolian: { IETF: 'mn-MN', ISO639: 'mn', ISO3166: 'mn' },
|
||||||
nepali: { IETF: 'ne-NP', 'ISO-639': 'ne' },
|
montenegrin: { IETF: 'sr-ME', ISO639: 'sr', ISO3166: 'me' },
|
||||||
'nigerian fulfulde': { IETF: 'fuv-NG', 'ISO-639': 'fuv' },
|
morisyen: { IETF: 'mfe-MU', ISO639: 'mfe', ISO3166: 'mu' },
|
||||||
niuean: { IETF: 'niu-NU', 'ISO-639': 'niu' },
|
'moroccan arabic': { IETF: 'ar-MA', ISO639: 'ar', ISO3166: 'ma' },
|
||||||
'north azerbaijani': { IETF: 'azj-AZ', 'ISO-639': 'azj' },
|
mossi: { IETF: 'mos-BF', ISO639: 'mos', ISO3166: 'bf' },
|
||||||
sesotho: { IETF: 'nso-ZA', 'ISO-639': 'nso' },
|
ndau: { IETF: 'ndc-MZ', ISO639: 'ndc', ISO3166: 'mz' },
|
||||||
'northern uzbek': { IETF: 'uzn-UZ', 'ISO-639': 'uzn' },
|
ndebele: { IETF: 'nr-ZA', ISO639: 'nr', ISO3166: 'za' },
|
||||||
'norwegian bokm<6B>l': { IETF: 'nb-NO', 'ISO-639': 'nb' },
|
nepali: { IETF: 'ne-NP', ISO639: 'ne', ISO3166: 'np' },
|
||||||
'norwegian nynorsk': { IETF: 'nn-NO', 'ISO-639': 'nn' },
|
'nigerian fulfulde': { IETF: 'fuv-NG', ISO639: 'fuv', ISO3166: 'ng' },
|
||||||
nuer: { IETF: 'nus-SS', 'ISO-639': 'nus' },
|
niuean: { IETF: 'niu-NU', ISO639: 'niu', ISO3166: 'nu' },
|
||||||
nyanja: { IETF: 'ny-MW', 'ISO-639': 'ny' },
|
'north azerbaijani': { IETF: 'azj-AZ', ISO639: 'azj', ISO3166: 'az' },
|
||||||
occitan: { IETF: 'oc-FR', 'ISO-639': 'oc' },
|
sesotho: { IETF: 'nso-ZA', ISO639: 'nso', ISO3166: 'za' },
|
||||||
'occitan aran': { IETF: 'oc-ES', 'ISO-639': 'oc' },
|
'northern uzbek': { IETF: 'uzn-UZ', ISO639: 'uzn', ISO3166: 'uz' },
|
||||||
odia: { IETF: 'or-IN', 'ISO-639': 'or' },
|
'norwegian bokm<6B>l': { IETF: 'nb-NO', ISO639: 'nb', ISO3166: 'no' },
|
||||||
oriya: { IETF: 'ory-IN', 'ISO-639': 'ory' },
|
'norwegian nynorsk': { IETF: 'nn-NO', ISO639: 'nn', ISO3166: 'no' },
|
||||||
urdu: { IETF: 'ur-PK', 'ISO-639': 'ur' },
|
nuer: { IETF: 'nus-SS', ISO639: 'nus', ISO3166: 'ss' },
|
||||||
palauan: { IETF: 'pau-PW', 'ISO-639': 'pau' },
|
nyanja: { IETF: 'ny-MW', ISO639: 'ny', ISO3166: 'mw' },
|
||||||
pali: { IETF: 'pi-IN', 'ISO-639': 'pi' },
|
occitan: { IETF: 'oc-FR', ISO639: 'oc', ISO3166: 'fr' },
|
||||||
pangasinan: { IETF: 'pag-PH', 'ISO-639': 'pag' },
|
'occitan aran': { IETF: 'oc-ES', ISO639: 'oc', ISO3166: 'es-ct' },
|
||||||
papiamentu: { IETF: 'pap-CW', 'ISO-639': 'pap' },
|
odia: { IETF: 'or-IN', ISO639: 'or', ISO3166: 'in' },
|
||||||
pashto: { IETF: 'ps-PK', 'ISO-639': 'ps' },
|
oriya: { IETF: 'ory-IN', ISO639: 'ory', ISO3166: 'in' },
|
||||||
persian: { IETF: 'fa-IR', 'ISO-639': 'fa' },
|
urdu: { IETF: 'ur-PK', ISO639: 'ur', ISO3166: 'pk' },
|
||||||
pijin: { IETF: 'pis-SB', 'ISO-639': 'pis' },
|
palauan: { IETF: 'pau-PW', ISO639: 'pau', ISO3166: 'pw' },
|
||||||
'plateau malagasy': { IETF: 'plt-MG', 'ISO-639': 'plt' },
|
pali: { IETF: 'pi-IN', ISO639: 'pi', ISO3166: 'in' },
|
||||||
polish: { IETF: 'pl-PL', 'ISO-639': 'pl' },
|
pangasinan: { IETF: 'pag-PH', ISO639: 'pag', ISO3166: 'ph' },
|
||||||
portuguese: { IETF: 'pt-PT', 'ISO-639': 'pt' },
|
papiamentu: { IETF: 'pap-CW', ISO639: 'pap', ISO3166: 'cw' },
|
||||||
'portuguese brazil': { IETF: 'pt-BR', 'ISO-639': 'pt' },
|
pashto: { IETF: 'ps-PK', ISO639: 'ps', ISO3166: 'pk' },
|
||||||
potawatomi: { IETF: 'pot-US', 'ISO-639': 'pot' },
|
persian: { IETF: 'fa-IR', ISO639: 'fa', ISO3166: 'ir' },
|
||||||
punjabi: { IETF: 'pa-IN', 'ISO-639': 'pa' },
|
pijin: { IETF: 'pis-SB', ISO639: 'pis', ISO3166: 'sb' },
|
||||||
'punjabi (pakistan)': { IETF: 'pnb-PK', 'ISO-639': 'pnb' },
|
'plateau malagasy': { IETF: 'plt-MG', ISO639: 'plt', ISO3166: 'mg' },
|
||||||
quechua: { IETF: 'qu-PE', 'ISO-639': 'qu' },
|
polish: { IETF: 'pl-PL', ISO639: 'pl', ISO3166: 'pl' },
|
||||||
rohingya: { IETF: 'rhg-MM', 'ISO-639': 'rhg' },
|
portuguese: { IETF: 'pt-PT', ISO639: 'pt', ISO3166: 'pt' },
|
||||||
rohingyalish: { IETF: 'rhl-MM', 'ISO-639': 'rhl' },
|
'portuguese brazil': { IETF: 'pt-BR', ISO639: 'pt', ISO3166: 'br' },
|
||||||
romanian: { IETF: 'ro-RO', 'ISO-639': 'ro' },
|
potawatomi: { IETF: 'pot-US', ISO639: 'pot', ISO3166: 'us' },
|
||||||
romansh: { IETF: 'roh-CH', 'ISO-639': 'roh' },
|
punjabi: { IETF: 'pa-IN', ISO639: 'pa', ISO3166: 'in' },
|
||||||
rundi: { IETF: 'run-BI', 'ISO-639': 'run' },
|
'punjabi (pakistan)': { IETF: 'pnb-PK', ISO639: 'pnb', ISO3166: 'pk' },
|
||||||
russian: { IETF: 'ru-RU', 'ISO-639': 'ru' },
|
quechua: { IETF: 'qu-PE', ISO639: 'qu', ISO3166: 'pe' },
|
||||||
'saint lucian creole french': { IETF: 'acf-LC', 'ISO-639': 'acf' },
|
rohingya: { IETF: 'rhg-MM', ISO639: 'rhg', ISO3166: 'mm' },
|
||||||
samoan: { IETF: 'sm-WS', 'ISO-639': 'sm' },
|
rohingyalish: { IETF: 'rhl-MM', ISO639: 'rhl', ISO3166: 'mm' },
|
||||||
sango: { IETF: 'sg-CF', 'ISO-639': 'sg' },
|
romanian: { IETF: 'ro-RO', ISO639: 'ro', ISO3166: 'ro' },
|
||||||
sanskrit: { IETF: 'sa-IN', 'ISO-639': 'sa' },
|
romansh: { IETF: 'roh-CH', ISO639: 'roh', ISO3166: 'ch' },
|
||||||
santali: { IETF: 'sat-IN', 'ISO-639': 'sat' },
|
rundi: { IETF: 'run-BI', ISO639: 'run', ISO3166: 'bi' },
|
||||||
sardinian: { IETF: 'sc-IT', 'ISO-639': 'sc' },
|
'saint lucian creole french': { IETF: 'acf-LC', ISO639: 'acf', ISO3166: 'lc' },
|
||||||
'scots gaelic': { IETF: 'gd-GB', 'ISO-639': 'gd' },
|
samoan: { IETF: 'sm-WS', ISO639: 'sm', ISO3166: 'ws' },
|
||||||
sena: { IETF: 'seh-ZW', 'ISO-639': 'seh' },
|
sango: { IETF: 'sg-CF', ISO639: 'sg', ISO3166: 'cf' },
|
||||||
'serbian cyrillic': { IETF: 'sr-Cyrl-RS', 'ISO-639': 'sr' },
|
sanskrit: { IETF: 'sa-IN', ISO639: 'sa', ISO3166: 'in' },
|
||||||
'serbian latin': { IETF: 'sr-Latn-RS', 'ISO-639': 'sr' },
|
santali: { IETF: 'sat-IN', ISO639: 'sat', ISO3166: 'in' },
|
||||||
'seselwa creole french': { IETF: 'crs-SC', 'ISO-639': 'crs' },
|
sardinian: { IETF: 'sc-IT', ISO639: 'sc', ISO3166: 'it' },
|
||||||
'setswana (south africa)': { IETF: 'tn-ZA', 'ISO-639': 'tn' },
|
'scots gaelic': { IETF: 'gd-GB', ISO639: 'gd', ISO3166: 'gb-sct' },
|
||||||
shan: { IETF: 'shn-MM', 'ISO-639': 'shn' },
|
sena: { IETF: 'seh-ZW', ISO639: 'seh', ISO3166: 'zw' },
|
||||||
shona: { IETF: 'sn-ZW', 'ISO-639': 'sn' },
|
'serbian cyrillic': { IETF: 'sr-Cyrl-RS', ISO639: 'sr', ISO3166: 'rs' },
|
||||||
sicilian: { IETF: 'scn-IT', 'ISO-639': 'scn' },
|
'serbian latin': { IETF: 'sr-Latn-RS', ISO639: 'sr', ISO3166: 'rs' },
|
||||||
silesian: { IETF: 'szl-PL', 'ISO-639': 'szl' },
|
'seselwa creole french': { IETF: 'crs-SC', ISO639: 'crs', ISO3166: 'sc' },
|
||||||
'sindhi snd': { IETF: 'snd-PK', 'ISO-639': 'snd' },
|
'setswana (south africa)': { IETF: 'tn-ZA', ISO639: 'tn', ISO3166: 'za' },
|
||||||
'sindhi sd': { IETF: 'sd-PK', 'ISO-639': 'sd' },
|
shan: { IETF: 'shn-MM', ISO639: 'shn', ISO3166: 'mm' },
|
||||||
sinhala: { IETF: 'si-LK', 'ISO-639': 'si' },
|
shona: { IETF: 'sn-ZW', ISO639: 'sn', ISO3166: 'zw' },
|
||||||
slovak: { IETF: 'sk-SK', 'ISO-639': 'sk' },
|
sicilian: { IETF: 'scn-IT', ISO639: 'scn', ISO3166: 'it' },
|
||||||
slovenian: { IETF: 'sl-SI', 'ISO-639': 'sl' },
|
silesian: { IETF: 'szl-PL', ISO639: 'szl', ISO3166: 'pl' },
|
||||||
somali: { IETF: 'so-SO', 'ISO-639': 'so' },
|
'sindhi snd': { IETF: 'snd-PK', ISO639: 'snd', ISO3166: 'pk' },
|
||||||
'sotho southern': { IETF: 'st-LS', 'ISO-639': 'st' },
|
'sindhi sd': { IETF: 'sd-PK', ISO639: 'sd', ISO3166: 'pk' },
|
||||||
'south azerbaijani': { IETF: 'azb-AZ', 'ISO-639': 'azb' },
|
sinhala: { IETF: 'si-LK', ISO639: 'si', ISO3166: 'lk' },
|
||||||
'southern pashto': { IETF: 'pbt-PK', 'ISO-639': 'pbt' },
|
slovak: { IETF: 'sk-SK', ISO639: 'sk', ISO3166: 'sk' },
|
||||||
'southwestern dinka': { IETF: 'dik-SS', 'ISO-639': 'dik' },
|
slovenian: { IETF: 'sl-SI', ISO639: 'sl', ISO3166: 'si' },
|
||||||
spanish: { IETF: 'es-ES', 'ISO-639': 'es' },
|
somali: { IETF: 'so-SO', ISO639: 'so', ISO3166: 'so' },
|
||||||
'spanish argentina': { IETF: 'es-AR', 'ISO-639': 'es' },
|
'sotho southern': { IETF: 'st-LS', ISO639: 'st', ISO3166: 'ls' },
|
||||||
'spanish colombia': { IETF: 'es-CO', 'ISO-639': 'es' },
|
'south azerbaijani': { IETF: 'azb-AZ', ISO639: 'azb', ISO3166: 'az' },
|
||||||
'spanish latin america': { IETF: 'es-419', 'ISO-639': 'es' },
|
'southern pashto': { IETF: 'pbt-PK', ISO639: 'pbt', ISO3166: 'pk' },
|
||||||
'spanish mexico': { IETF: 'es-MX', 'ISO-639': 'es' },
|
'southwestern dinka': { IETF: 'dik-SS', ISO639: 'dik', ISO3166: 'ss' },
|
||||||
'spanish united states': { IETF: 'es-US', 'ISO-639': 'es' },
|
'spanish argentina': { IETF: 'es-AR', ISO639: 'es', ISO3166: 'ar' },
|
||||||
'sranan tongo': { IETF: 'srn-SR', 'ISO-639': 'srn' },
|
'spanish colombia': { IETF: 'es-CO', ISO639: 'es', ISO3166: 'co' },
|
||||||
'standard latvian': { IETF: 'lvs-LV', 'ISO-639': 'lvs' },
|
'spanish latin america': { IETF: 'es-419', ISO639: 'es', ISO3166: 'do' },
|
||||||
'standard malay': { IETF: 'zsm-MY', 'ISO-639': 'zsm' },
|
'spanish mexico': { IETF: 'es-MX', ISO639: 'es', ISO3166: 'mx' },
|
||||||
sundanese: { IETF: 'su-ID', 'ISO-639': 'su' },
|
'spanish united states': { IETF: 'es-US', ISO639: 'es', ISO3166: 'es' },
|
||||||
swahili: { IETF: 'sw-KE', 'ISO-639': 'sw' },
|
'sranan tongo': { IETF: 'srn-SR', ISO639: 'srn', ISO3166: 'sr' },
|
||||||
swati: { IETF: 'ss-SZ', 'ISO-639': 'ss' },
|
'standard latvian': { IETF: 'lvs-LV', ISO639: 'lvs', ISO3166: 'lv' },
|
||||||
swedish: { IETF: 'sv-SE', 'ISO-639': 'sv' },
|
'standard malay': { IETF: 'zsm-MY', ISO639: 'zsm', ISO3166: 'my' },
|
||||||
'swiss german': { IETF: 'de-CH', 'ISO-639': 'de' },
|
sundanese: { IETF: 'su-ID', ISO639: 'su', ISO3166: 'id' },
|
||||||
'syriac (aramaic)': { IETF: 'syc-TR', 'ISO-639': 'syc' },
|
swahili: { IETF: 'sw-KE', ISO639: 'sw', ISO3166: 'ke' },
|
||||||
tagalog: { IETF: 'tl-PH', 'ISO-639': 'tl' },
|
swati: { IETF: 'ss-SZ', ISO639: 'ss', ISO3166: 'sz' },
|
||||||
tahitian: { IETF: 'ty-PF', 'ISO-639': 'ty' },
|
swedish: { IETF: 'sv-SE', ISO639: 'sv', ISO3166: 'se' },
|
||||||
tajik: { IETF: 'tg-TJ', 'ISO-639': 'tg' },
|
'swiss german': { IETF: 'de-CH', ISO639: 'de', ISO3166: 'ch' },
|
||||||
'tamashek (tuareg)': { IETF: 'tmh-DZ', 'ISO-639': 'tmh' },
|
'syriac (aramaic)': { IETF: 'syc-TR', ISO639: 'syc', ISO3166: 'tr' },
|
||||||
tamasheq: { IETF: 'taq-ML', 'ISO-639': 'taq' },
|
tagalog: { IETF: 'tl-PH', ISO639: 'tl', ISO3166: 'ph' },
|
||||||
'tamil india': { IETF: 'ta-IN', 'ISO-639': 'ta' },
|
tahitian: { IETF: 'ty-PF', ISO639: 'ty', ISO3166: 'pf' },
|
||||||
'tamil sri lanka': { IETF: 'ta-LK', 'ISO-639': 'ta' },
|
tajik: { IETF: 'tg-TJ', ISO639: 'tg', ISO3166: 'tj' },
|
||||||
taroko: { IETF: 'trv-TW', 'ISO-639': 'trv' },
|
'tamashek (tuareg)': { IETF: 'tmh-DZ', ISO639: 'tmh', ISO3166: 'dz' },
|
||||||
tatar: { IETF: 'tt-RU', 'ISO-639': 'tt' },
|
tamasheq: { IETF: 'taq-ML', ISO639: 'taq', ISO3166: 'ml' },
|
||||||
telugu: { IETF: 'te-IN', 'ISO-639': 'te' },
|
'tamil india': { IETF: 'ta-IN', ISO639: 'ta', ISO3166: 'in' },
|
||||||
tetum: { IETF: 'tet-TL', 'ISO-639': 'tet' },
|
'tamil sri lanka': { IETF: 'ta-LK', ISO639: 'ta', ISO3166: 'lk' },
|
||||||
thai: { IETF: 'th-TH', 'ISO-639': 'th' },
|
taroko: { IETF: 'trv-TW', ISO639: 'trv', ISO3166: 'tw' },
|
||||||
tibetan: { IETF: 'bo-CN', 'ISO-639': 'bo' },
|
tatar: { IETF: 'tt-RU', ISO639: 'tt', ISO3166: 'ru' },
|
||||||
tigrinya: { IETF: 'ti-ET', 'ISO-639': 'ti' },
|
telugu: { IETF: 'te-IN', ISO639: 'te', ISO3166: 'in' },
|
||||||
'tok pisin': { IETF: 'tpi-PG', 'ISO-639': 'tpi' },
|
tetum: { IETF: 'tet-TL', ISO639: 'tet', ISO3166: 'tl' },
|
||||||
tokelauan: { IETF: 'tkl-TK', 'ISO-639': 'tkl' },
|
thai: { IETF: 'th-TH', ISO639: 'th', ISO3166: 'th' },
|
||||||
tongan: { IETF: 'to-TO', 'ISO-639': 'to' },
|
tibetan: { IETF: 'bo-CN', ISO639: 'bo', ISO3166: 'cn' },
|
||||||
'tosk albanian': { IETF: 'als-AL', 'ISO-639': 'als' },
|
tigrinya: { IETF: 'ti-ET', ISO639: 'ti', ISO3166: 'et' },
|
||||||
tsonga: { IETF: 'ts-ZA', 'ISO-639': 'ts' },
|
'tok pisin': { IETF: 'tpi-PG', ISO639: 'tpi', ISO3166: 'pg' },
|
||||||
tswa: { IETF: 'tsc-MZ', 'ISO-639': 'tsc' },
|
tokelauan: { IETF: 'tkl-TK', ISO639: 'tkl', ISO3166: 'tk' },
|
||||||
tswana: { IETF: 'tn-BW', 'ISO-639': 'tn' },
|
tongan: { IETF: 'to-TO', ISO639: 'to', ISO3166: 'to' },
|
||||||
tumbuka: { IETF: 'tum-MW', 'ISO-639': 'tum' },
|
'tosk albanian': { IETF: 'als-AL', ISO639: 'als', ISO3166: 'al' },
|
||||||
turkish: { IETF: 'tr-TR', 'ISO-639': 'tr' },
|
tsonga: { IETF: 'ts-ZA', ISO639: 'ts', ISO3166: 'za' },
|
||||||
turkmen: { IETF: 'tk-TM', 'ISO-639': 'tk' },
|
tswa: { IETF: 'tsc-MZ', ISO639: 'tsc', ISO3166: 'mz' },
|
||||||
tuvaluan: { IETF: 'tvl-TV', 'ISO-639': 'tvl' },
|
tswana: { IETF: 'tn-BW', ISO639: 'tn', ISO3166: 'bw' },
|
||||||
twi: { IETF: 'tw-GH', 'ISO-639': 'tw' },
|
tumbuka: { IETF: 'tum-MW', ISO639: 'tum', ISO3166: 'mw' },
|
||||||
udmurt: { IETF: 'udm-RU', 'ISO-639': 'udm' },
|
turkmen: { IETF: 'tk-TM', ISO639: 'tk', ISO3166: 'tm' },
|
||||||
ukrainian: { IETF: 'uk-UA', 'ISO-639': 'uk' },
|
tuvaluan: { IETF: 'tvl-TV', ISO639: 'tvl', ISO3166: 'tv' },
|
||||||
uma: { IETF: 'ppk-ID', 'ISO-639': 'ppk' },
|
twi: { IETF: 'tw-GH', ISO639: 'tw', ISO3166: 'gh' },
|
||||||
umbundu: { IETF: 'umb-AO', 'ISO-639': 'umb' },
|
udmurt: { IETF: 'udm-RU', ISO639: 'udm', ISO3166: 'xx' },
|
||||||
'uyghur uig': { IETF: 'uig-CN', 'ISO-639': 'uig' },
|
ukrainian: { IETF: 'uk-UA', ISO639: 'uk', ISO3166: 'ua' },
|
||||||
'uyghur ug': { IETF: 'ug-CN', 'ISO-639': 'ug' },
|
uma: { IETF: 'ppk-ID', ISO639: 'ppk', ISO3166: 'id' },
|
||||||
uzbek: { IETF: 'uz-UZ', 'ISO-639': 'uz' },
|
umbundu: { IETF: 'umb-AO', ISO639: 'umb', ISO3166: 'ao' },
|
||||||
venetian: { IETF: 'vec-IT', 'ISO-639': 'vec' },
|
'uyghur uig': { IETF: 'uig-CN', ISO639: 'uig', ISO3166: 'cn' },
|
||||||
vietnamese: { IETF: 'vi-VN', 'ISO-639': 'vi' },
|
'uyghur ug': { IETF: 'ug-CN', ISO639: 'ug', ISO3166: 'cn' },
|
||||||
'vincentian creole english': { IETF: 'svc-VC', 'ISO-639': 'svc' },
|
uzbek: { IETF: 'uz-UZ', ISO639: 'uz', ISO3166: 'uz' },
|
||||||
'virgin islands creole english': { IETF: 'vic-US', 'ISO-639': 'vic' },
|
venetian: { IETF: 'vec-IT', ISO639: 'vec', ISO3166: 'it' },
|
||||||
wallisian: { IETF: 'wls-WF', 'ISO-639': 'wls' },
|
vietnamese: { IETF: 'vi-VN', ISO639: 'vi', ISO3166: 'vn' },
|
||||||
'waray (philippines)': { IETF: 'war-PH', 'ISO-639': 'war' },
|
'vincentian creole english': { IETF: 'svc-VC', ISO639: 'svc', ISO3166: 'vc' },
|
||||||
welsh: { IETF: 'cy-GB', 'ISO-639': 'cy' },
|
'virgin islands creole english': { IETF: 'vic-US', ISO639: 'vic', ISO3166: 'vi' },
|
||||||
'west central oromo': { IETF: 'gaz-ET', 'ISO-639': 'gaz' },
|
wallisian: { IETF: 'wls-WF', ISO639: 'wls', ISO3166: 'wf' },
|
||||||
'western persian': { IETF: 'pes-IR', 'ISO-639': 'pes' },
|
'waray (philippines)': { IETF: 'war-PH', ISO639: 'war', ISO3166: 'ph' },
|
||||||
wolof: { IETF: 'wo-SN', 'ISO-639': 'wo' },
|
welsh: { IETF: 'cy-GB', ISO639: 'cy', ISO3166: 'gb-wls' },
|
||||||
xhosa: { IETF: 'xh-ZA', 'ISO-639': 'xh' },
|
'west central oromo': { IETF: 'gaz-ET', ISO639: 'gaz', ISO3166: 'et' },
|
||||||
yiddish: { IETF: 'yi-YD', 'ISO-639': 'yi' },
|
'western persian': { IETF: 'pes-IR', ISO639: 'pes', ISO3166: 'ir' },
|
||||||
yoruba: { IETF: 'yo-NG', 'ISO-639': 'yo' },
|
wolof: { IETF: 'wo-SN', ISO639: 'wo', ISO3166: 'sn' },
|
||||||
zulu: { IETF: 'zu-ZA', 'ISO-639': 'zu' },
|
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 };
|
module.exports = { languages };
|
||||||
|
|
|
||||||
102
src/js/logger.js
|
|
@ -6,73 +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} ${JSON.stringify(info.metadata)}`),
|
format.printf(info => `${info.timestamp} - ${info.level}: ${info.message} ${JSON.stringify(info.metadata)}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileFormat = format.combine(
|
const fileFormat = format.combine(
|
||||||
format.timestamp(),
|
format.timestamp(),
|
||||||
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }),
|
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }),
|
||||||
format.json(),
|
format.json()
|
||||||
);
|
);
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
format: fileFormat,
|
format: fileFormat,
|
||||||
transports: [
|
transports: [
|
||||||
new transports.File({
|
new transports.File({
|
||||||
filename: path.join(__dirname, '../logs/error.log'),
|
filename: path.join(__dirname, '../logs/error.log'),
|
||||||
level: 'error',
|
level: 'error'
|
||||||
}),
|
}),
|
||||||
new transports.File({
|
new transports.File({
|
||||||
filename: path.join(__dirname, '../logs/activity.log'),
|
filename: path.join(__dirname, '../logs/activity.log'),
|
||||||
maxsize: 5242880,
|
maxsize: 5242880,
|
||||||
maxFiles: 5,
|
maxFiles: 5
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
logger.add(
|
logger.add(
|
||||||
new transports.Console({
|
new transports.Console({
|
||||||
level: consoleloggerLevel,
|
level: consoleloggerLevel,
|
||||||
format: consoleFormat,
|
format: consoleFormat
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(path.join(__dirname, '../logs/activity.log'))
|
fetch(path.join(__dirname, '../logs/activity.log'))
|
||||||
.then((response) => response.text())
|
.then(response => response.text())
|
||||||
.then((logData) => {
|
.then(logData => {
|
||||||
const logLines = logData.trim().split('\n');
|
const logLines = logData.trim().split('\n');
|
||||||
const tableBody = document.getElementById('logContent');
|
const tableBody = document.getElementById('logContent');
|
||||||
|
|
||||||
logLines.forEach((logLine) => {
|
logLines.forEach(logLine => {
|
||||||
const logObject = JSON.parse(logLine);
|
const logObject = JSON.parse(logLine);
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
|
|
||||||
const levelCell = document.createElement('td');
|
const levelCell = document.createElement('td');
|
||||||
levelCell.textContent = logObject.level;
|
levelCell.textContent = logObject.level;
|
||||||
levelCell.classList.add(logObject.level); // Add class for styling
|
levelCell.classList.add(logObject.level); // Add class for styling
|
||||||
row.appendChild(levelCell);
|
row.appendChild(levelCell);
|
||||||
|
|
||||||
const messageCell = document.createElement('td');
|
const messageCell = document.createElement('td');
|
||||||
messageCell.textContent = logObject.message;
|
messageCell.textContent = logObject.message;
|
||||||
row.appendChild(messageCell);
|
row.appendChild(messageCell);
|
||||||
|
|
||||||
const metadataCell = document.createElement('td');
|
const metadataCell = document.createElement('td');
|
||||||
metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2);
|
metadataCell.textContent = JSON.stringify(logObject.metadata, null, 2);
|
||||||
row.appendChild(metadataCell);
|
row.appendChild(metadataCell);
|
||||||
|
|
||||||
const timestampCell = document.createElement('td');
|
const timestampCell = document.createElement('td');
|
||||||
timestampCell.textContent = logObject.timestamp;
|
timestampCell.textContent = logObject.timestamp;
|
||||||
row.appendChild(timestampCell);
|
row.appendChild(timestampCell);
|
||||||
|
|
||||||
tableBody.appendChild(row);
|
tableBody.appendChild(row);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = logger;
|
module.exports = logger;
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,51 @@
|
||||||
let micSelect = document.querySelector('#microphone');
|
/* global settings, */
|
||||||
|
|
||||||
|
const micSelect = document.querySelector('#microphone');
|
||||||
let selectedMic;
|
let selectedMic;
|
||||||
|
|
||||||
function getAvailableMediaDevices(type) {
|
function getAvailableMediaDevices(type) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
navigator.mediaDevices
|
navigator.mediaDevices
|
||||||
.enumerateDevices()
|
.enumerateDevices()
|
||||||
.then((devices) => {
|
.then(devices => {
|
||||||
const microphones = devices.filter((device) => device.kind === type);
|
const microphones = devices.filter(device => device.kind === type);
|
||||||
resolve(microphones);
|
resolve(microphones);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Microphones
|
// Microphones
|
||||||
getAvailableMediaDevices('audioinput')
|
getAvailableMediaDevices('audioinput')
|
||||||
.then((microphones) => {
|
.then(microphones => {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let tempname = '';
|
let tempname = '';
|
||||||
for (let mic of microphones) {
|
for (const mic of microphones) {
|
||||||
if (mic.deviceId === 'default') {
|
if (mic.deviceId === 'default') {
|
||||||
tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name.
|
tempname = mic.label.slice(10); // remove "default -" from the label to get the default device name.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mic.deviceId === 'communications' || mic.label === tempname) {
|
if (mic.deviceId === 'communications' || mic.label === tempname) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
|
|
||||||
// Set the options value and text.
|
// Set the options value and text.
|
||||||
option.value = i;
|
option.value = i;
|
||||||
option.innerHTML = `${mic.label}`;
|
option.innerHTML = `${mic.label}`;
|
||||||
|
|
||||||
// Add the option to the voice selector.
|
// Add the option to the voice selector.
|
||||||
micSelect.appendChild(option);
|
micSelect.appendChild(option);
|
||||||
|
|
||||||
if (i === microphones.length - 1) {
|
if (i === microphones.length - 1) {
|
||||||
document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE;
|
document.getElementById('microphone').value = settings.STT.SELECTED_MICROPHONE;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
console.error('Error retrieving microphones:', error);
|
console.error('Error retrieving microphones:', error);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,51 @@
|
||||||
const twitchTemplate = `
|
const twitchTemplate = `
|
||||||
<div class="icon-container">
|
<img class="user-img" src="" />
|
||||||
<img class="user-img" src="" />
|
<img class="status-circle sender" src="./images/twitch-icon.png" tip="Twitch" />
|
||||||
<img class="status-circle" src="./images/twitch-icon.png" />
|
<span class="post-time sender"></span>
|
||||||
</div>
|
<span class="username sender"></span>
|
||||||
<span class="username"></span>
|
<div class="msg-box sender"></div>
|
||||||
<div class="msg-box">
|
`.trim();
|
||||||
</div>
|
|
||||||
|
const trovoTemplate = `
|
||||||
|
<img class="user-img" src="" />
|
||||||
|
<img class="status-circle sender" src="./images/trovo-icon.jpg" tip="Trovo" />
|
||||||
|
<span class="post-time sender"></span>
|
||||||
|
<span class="username sender"></span>
|
||||||
|
<div class="msg-box sender"></div>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const youtubeTemplate = `
|
||||||
|
<img class="user-img" src="" />
|
||||||
|
<img class="status-circle sender" src="./images/youtube-icon.png" tip="Youtube" />
|
||||||
|
<span class="post-time sender"></span>
|
||||||
|
<span class="username sender"></span>
|
||||||
|
<div class="msg-box sender"></div>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const dliveTemplate = `
|
||||||
|
<img class="user-img" src="" />
|
||||||
|
<img class="status-circle sender" src="./images/dlive-icon.png" tip="DLive" />
|
||||||
|
<span class="post-time sender"></span>
|
||||||
|
<span class="username sender"></span>
|
||||||
|
<div class="msg-box sender"></div>
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
const userTemplate = `
|
const userTemplate = `
|
||||||
<div class="icon-container-user">
|
<img class="user-img" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
||||||
<span class="post-time-user">You</span>
|
<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>
|
||||||
<img class="user-img-user" src="https://gravatar.com/avatar/56234674574535734573000000000001?d=retro" />
|
<span class="username user">You</span>
|
||||||
</div>
|
<div class="msg-box user"></div>
|
||||||
<span class="username-user">You</span>
|
|
||||||
<div class="msg-box-user">
|
|
||||||
</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,14 +1,12 @@
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const ini = require('ini');
|
const path = require('path');
|
||||||
const path = require('path'); // get directory path
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const Sockette = require('sockette');
|
||||||
|
|
||||||
const { ipcRenderer, shell } = require('electron'); // necessary electron libraries to send data to the app
|
const { webFrame, ipcRenderer, shell } = require('electron');
|
||||||
const io = require('socket.io-client');
|
const io = require('socket.io-client');
|
||||||
|
|
||||||
const util = require('util');
|
|
||||||
const exec = util.promisify(require('child_process').exec);
|
|
||||||
|
|
||||||
const GoogleTTS = require('node-google-tts-api');
|
const GoogleTTS = require('node-google-tts-api');
|
||||||
|
|
||||||
const tts = new GoogleTTS();
|
const tts = new GoogleTTS();
|
||||||
|
|
@ -17,14 +15,16 @@ const { Socket } = require('socket.io-client');
|
||||||
const main = ipcRenderer.sendSync('environment');
|
const main = ipcRenderer.sendSync('environment');
|
||||||
|
|
||||||
const resourcesPath = main.resourcesPath;
|
const resourcesPath = main.resourcesPath;
|
||||||
let settingsPath = main.settingsPath.toString();
|
const settingsPath = main.settingsPath.toString();
|
||||||
let pythonPath = main.pythonPath.toString();
|
const pythonPath = main.pythonPath.toString();
|
||||||
const settings = main.settings;
|
const settings = main.settings;
|
||||||
|
|
||||||
// TODO: remove gooogle voices txt and use api instead
|
// TODO: remove gooogle voices txt and use api instead
|
||||||
const googleVoices = fs.readFileSync(path.join(__dirname, './config/googleVoices.txt')).toString().split('\r\n');
|
const googleVoices = fs.readFileSync(path.join(__dirname, './config/googleVoices.txt')).toString().split('\r\n');
|
||||||
// TODO: remove amazon voices txt and use api instead (sakura project has it)
|
// TODO: remove amazon voices txt and use api instead (sakura project has it)
|
||||||
const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n');
|
const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n');
|
||||||
|
const customEmotesListSavePath =
|
||||||
|
main.isPackaged === true ? path.join(resourcesPath, './custom-emotes.json') : path.join(resourcesPath, './config/custom-emotes.json');
|
||||||
|
|
||||||
// html elements
|
// html elements
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
|
@ -35,6 +35,9 @@ const devicesDropdown = document.querySelector('#devicesDropdown');
|
||||||
const notificationSound = document.querySelector('#notification'); // obtain the html reference of the sound comboBox
|
const notificationSound = document.querySelector('#notification'); // obtain the html reference of the sound comboBox
|
||||||
const sttModel = document.querySelector('#sttModel'); // obtain the html reference of the sound comboBox
|
const sttModel = document.querySelector('#sttModel'); // obtain the html reference of the sound comboBox
|
||||||
const ttsAudioDevices = document.querySelector('#ttsAudioDevice'); // obtain the html reference of the installedTTS comboBox
|
const ttsAudioDevices = document.querySelector('#ttsAudioDevice'); // obtain the html reference of the installedTTS comboBox
|
||||||
|
const notificationSoundAudioDevices = document.querySelector('#notificationSoundAudioDevice'); // obtain the html reference of the installedTTS comboBox
|
||||||
|
const emojiPicker = document.body.querySelector('emoji-picker');
|
||||||
|
const lol = document.body.querySelector('country-flag-emoji-polyfill');
|
||||||
|
|
||||||
// laod local javascript files
|
// laod local javascript files
|
||||||
const chat = require(path.join(__dirname, './js/chat'));
|
const chat = require(path.join(__dirname, './js/chat'));
|
||||||
|
|
@ -44,21 +47,43 @@ const languageObject = require(path.join(__dirname, './js/languages'));
|
||||||
const logger = require(path.join(__dirname, './js/logger'));
|
const logger = require(path.join(__dirname, './js/logger'));
|
||||||
const sound = require(path.join(__dirname, './js/sound'));
|
const sound = require(path.join(__dirname, './js/sound'));
|
||||||
const config = require(path.join(__dirname, './js/settings'));
|
const config = require(path.join(__dirname, './js/settings'));
|
||||||
|
const { TokenAutocomplete } = require(path.join(__dirname, './js/token-autocomplete'));
|
||||||
|
|
||||||
|
const betterTTVAutocomplete = new TokenAutocomplete({
|
||||||
|
name: 'sample',
|
||||||
|
selector: '#sample',
|
||||||
|
noMatchesText: 'No matching results...',
|
||||||
|
initialTokens: [...settings.TWITCH.BETTERTTV_CHANNELS]
|
||||||
|
});
|
||||||
|
|
||||||
|
const test = settings.TWITCH.BETTERTTV_CHANNELS;
|
||||||
|
console.log(test);
|
||||||
|
|
||||||
const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
|
const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
|
||||||
|
|
||||||
let notificationSounds = path.join(__dirname, './sounds/notifications');
|
const notificationSounds = path.join(resourcesPath, main.isPackaged ? './sounds/notifications' : '../sounds/notifications');
|
||||||
let sttModels = path.join(__dirname, '../speech_to_text_models');
|
const sttModels = path.join(resourcesPath, main.isPackaged ? './speech_to_text_models' : '../speech_to_text_models');
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
ipcRenderer.send('restart');
|
ipcRenderer.send('restart');
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = require(path.join(__dirname, './js/server'));
|
const server = require(path.join(__dirname, './js/server'));
|
||||||
const backend = require(path.join(__dirname, './js/backend'));
|
const backend = require(path.join(__dirname, './js/backend'));
|
||||||
let socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server
|
const socket = io(`http://localhost:${settings.GENERAL.PORT}`); // Connect to your Socket.IO server
|
||||||
|
|
||||||
|
let twitch = null;
|
||||||
|
twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
|
||||||
|
|
||||||
|
let dlive = null;
|
||||||
|
dlive = settings.DLIVE.USE_DLIVE ? require(path.join(__dirname, './js/dlive')) : '';
|
||||||
|
|
||||||
|
let youtube = null;
|
||||||
|
youtube = settings.YOUTUBE.USE_YOUTUBE ? require(path.join(__dirname, './js/youtube')) : '';
|
||||||
|
|
||||||
|
let trovo = null;
|
||||||
|
trovo = settings.TROVO.USE_TROVO ? require(path.join(__dirname, './js/trovo')) : '';
|
||||||
|
|
||||||
let twitch = settings.TWITCH.USE_TWITCH ? require(path.join(__dirname, './js/twitch')) : '';
|
|
||||||
const Polly = settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : '';
|
const Polly = settings.AMAZON.USE_AMAZON ? require(path.join(__dirname, './js/amazon')) : '';
|
||||||
const google = settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/google')) : '';
|
const google = settings.GOOGLE.USE_GOOGLE ? require(path.join(__dirname, './js/google')) : '';
|
||||||
|
|
||||||
|
|
@ -66,6 +91,12 @@ const theme = require(path.join(__dirname, './js/theme'));
|
||||||
const auth = require(path.join(__dirname, './js/auth'));
|
const auth = require(path.join(__dirname, './js/auth'));
|
||||||
|
|
||||||
let ttsRequestCount = 0;
|
let ttsRequestCount = 0;
|
||||||
|
ttsRequestCount = 0;
|
||||||
|
let customEmojis = [];
|
||||||
|
customEmojis = [];
|
||||||
|
let messageId = 0;
|
||||||
|
messageId = 0;
|
||||||
|
let customEmojiList = [];
|
||||||
|
|
||||||
// initialize values
|
// initialize values
|
||||||
config.getGeneralSettings();
|
config.getGeneralSettings();
|
||||||
|
|
@ -77,179 +108,472 @@ const StartDateAndTime = Date.now();
|
||||||
const speakButton = document.querySelector('#speakBtn');
|
const speakButton = document.querySelector('#speakBtn');
|
||||||
|
|
||||||
const amazonCredentials = {
|
const amazonCredentials = {
|
||||||
accessKeyId: settings.AMAZON.ACCESS_KEY,
|
accessKeyId: settings.AMAZON.ACCESS_KEY,
|
||||||
secretAccessKey: settings.AMAZON.ACCESS_SECRET,
|
secretAccessKey: settings.AMAZON.ACCESS_SECRET
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for installed sounds
|
// Check for installed sounds
|
||||||
fs.readdir(notificationSounds, (err, files) => {
|
fs.readdir(notificationSounds, (err, files) => {
|
||||||
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 = settings.AUDIO.NOTIFICATION_SOUND;
|
notificationSound.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// set the saved notification sound
|
||||||
|
notificationSound.selectedIndex = settings.AUDIO.NOTIFICATION_SOUND;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for installed stt models
|
// Check for installed stt models
|
||||||
fs.readdir(sttModels, (err, files) => {
|
fs.readdir(sttModels, (err, files) => {
|
||||||
for (let file of files) {
|
if (err) {
|
||||||
if (file.includes('.txt')) {
|
console.error(err);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
// Create a new option element.
|
|
||||||
const option = document.createElement('option');
|
|
||||||
|
|
||||||
// Set the options value and text.
|
for (const file of files) {
|
||||||
option.value = file;
|
if (file.includes('.txt')) {
|
||||||
option.innerHTML = file;
|
continue;
|
||||||
|
|
||||||
// Add the option to the sound selector.
|
|
||||||
sttModel.appendChild(option);
|
|
||||||
}
|
}
|
||||||
|
// Create a new option element.
|
||||||
|
const option = document.createElement('option');
|
||||||
|
|
||||||
// set the saved notification sound
|
// Set the options value and text.
|
||||||
sttModel.value = settings.STT.LANGUAGE;
|
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) {
|
||||||
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 = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE;
|
ttsAudioDevices.selectedIndex = settings.AUDIO.SELECTED_TTS_AUDIO_DEVICE;
|
||||||
|
notificationSoundAudioDevices.selectedIndex = settings.AUDIO.SELECTED_NOTIFICATION_AUDIO_DEVICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAudioDevices();
|
getAudioDevices();
|
||||||
|
|
||||||
function setLanguagesinSelect(languageSelector, setting) {
|
function setSelectedLanguageinSelect(languageSelect, language) {
|
||||||
let languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
|
const button = languageSelect.querySelector('.SmallButton');
|
||||||
|
const languageElement = document.createElement('span');
|
||||||
for (const language in languageObject.languages) {
|
languageElement.classList = `fi fi-${language.ISO3166} fis pop-selection`;
|
||||||
if (languageObject.languages.hasOwnProperty(language)) {
|
languageElement.setAttribute('tip', language.name);
|
||||||
const iso639 = languageObject.languages[language]['ISO-639'];
|
button.innerHTML = '';
|
||||||
const option = document.createElement('option');
|
button.appendChild(languageElement);
|
||||||
option.value = iso639;
|
addSingleTooltip(languageElement);
|
||||||
option.innerHTML = `${iso639} - ${language}`;
|
|
||||||
languageSelect.appendChild(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
languageSelect.selectedIndex = setting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE);
|
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) {
|
||||||
|
const languageSelect = document.querySelector(languageSelector); // obtain the html reference of the google voices comboBox
|
||||||
|
|
||||||
|
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 option = document.createElement('option');
|
||||||
|
|
||||||
|
option.value = IETF;
|
||||||
|
option.innerHTML = `${ISO639} : ${language}`;
|
||||||
|
languageSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
languageSelect.selectedIndex = setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLanguagesinSelect('#language', settings.GENERAL.LANGUAGE_INDEX);
|
||||||
setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX);
|
setLanguagesinSelect('#defaultLanguage', settings.TTS.PRIMARY_TTS_LANGUAGE_INDEX);
|
||||||
setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX);
|
setLanguagesinSelect('#secondaryLanguage', settings.TTS.SECONDARY_TTS_LANGUAGE_INDEX);
|
||||||
|
setLanguagesinSelect('#TRANSLATE_TO', settings.LANGUAGE.TRANSLATE_TO_INDEX);
|
||||||
|
|
||||||
function addVoiceService(name) {
|
function addVoiceService(name) {
|
||||||
function addToselect(select) {
|
function addToselect(select) {
|
||||||
let ttsService = document.querySelector(select);
|
const ttsService = document.querySelector(select);
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
ttsService.appendChild(option);
|
ttsService.appendChild(option);
|
||||||
|
|
||||||
option.value = name;
|
option.value = name;
|
||||||
option.innerHTML = name;
|
option.innerHTML = name;
|
||||||
}
|
}
|
||||||
addToselect('#primaryTTSService');
|
addToselect('#primaryTTSService');
|
||||||
addToselect('#secondaryTTSService');
|
addToselect('#secondaryTTSService');
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineTootlTipPosition(element) {
|
||||||
|
const horizontal = document.body.clientWidth / 2;
|
||||||
|
const vertical = document.body.clientHeight / 2;
|
||||||
|
|
||||||
|
element.tip.style.left = `${element.mouse.x}px`;
|
||||||
|
element.tip.style.top = `${element.mouse.y}px`;
|
||||||
|
|
||||||
|
const tipPosition = element.tip.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (element.position.x < horizontal && element.position.y < vertical) {
|
||||||
|
element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.position.x < horizontal && element.position.y > vertical) {
|
||||||
|
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) + 10}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.position.x > horizontal && element.position.y < vertical) {
|
||||||
|
element.tip.style.top = `${parseInt(element.tip.style.top) + 25}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.position.x > horizontal && element.position.y > vertical) {
|
||||||
|
element.tip.style.top = `${parseInt(element.tip.style.top) - tipPosition.height}px`;
|
||||||
|
element.tip.style.left = `${parseInt(element.tip.style.left) - tipPosition.width}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.tip.style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small tooltip
|
// Small tooltip
|
||||||
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'}, ${
|
const image = document.createElement('img');
|
||||||
el.hasAttribute('tip-top') ? '-100%' : '15px'
|
image.src = el.src;
|
||||||
})`;
|
tip.appendChild(image);
|
||||||
body.appendChild(tip);
|
}
|
||||||
element.onmousemove = (e) => {
|
body.appendChild(tip);
|
||||||
tip.style.left = `${e.x}px`;
|
tip.pointerEvents = 'none';
|
||||||
tip.style.top = `${e.y}px`;
|
element.onmousemove = e => {
|
||||||
tip.style.zIndex = 1;
|
determineTootlTipPosition({
|
||||||
tip.style.visibility = 'visible';
|
position: element.getBoundingClientRect(),
|
||||||
};
|
mouse: { x: e.x, y: e.y },
|
||||||
element.onmouseleave = (e) => {
|
tip
|
||||||
tip.style.visibility = 'hidden';
|
});
|
||||||
};
|
};
|
||||||
|
element.onmouseleave = e => {
|
||||||
|
tip.style.visibility = 'hidden';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(document.body.querySelectorAll('[tip]')).forEach(el => {
|
||||||
|
addSingleTooltip(el);
|
||||||
});
|
});
|
||||||
|
|
||||||
function showChatMessage(article, isUser) {
|
function showChatMessage(article) {
|
||||||
document.querySelector('#chatBox').appendChild(article);
|
let body = null;
|
||||||
let usernameHtml;
|
if (article !== undefined) {
|
||||||
let msg;
|
body = document.getElementById('chatBox');
|
||||||
let messages = Array.from(document.body.querySelectorAll('.msg-container'));
|
body.appendChild(article);
|
||||||
|
}
|
||||||
|
|
||||||
if (isUser) {
|
const messages = document.body.querySelectorAll('.msg-container');
|
||||||
usernameHtml = article.querySelector('.username-user');
|
|
||||||
msg = article.querySelector('.msg-box-user');
|
|
||||||
} else {
|
|
||||||
usernameHtml = article.querySelector('.username');
|
|
||||||
msg = article.querySelector('.msg-box');
|
|
||||||
}
|
|
||||||
|
|
||||||
var style = getComputedStyle(usernameHtml);
|
const lastMessage = messages[messages.length - 1];
|
||||||
var style2 = getComputedStyle(usernameHtml);
|
lastMessage.scrollIntoView({ block: 'end', behavior: 'smooth' });
|
||||||
|
|
||||||
const lastMessage = messages[messages.length - 1];
|
// const messageId = article.id;
|
||||||
lastMessage.scrollIntoView({ behavior: 'smooth' });
|
// 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();
|
||||||
var ampm = hours >= 12 ? 'PM' : 'AM';
|
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||||
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
|
||||||
const time = `${hours}:${minutes} ${ampm}`;
|
const time = `${hours}:${minutes} ${ampm}`;
|
||||||
|
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreviewChatMessage() {
|
function showPreviewChatMessage() {
|
||||||
const message = messageTemplates.messageTemplate;
|
const message = messageTemplates.messageTemplate;
|
||||||
document.querySelector('#mini-mid').innerHTML += message;
|
document.querySelector('#mini-mid').innerHTML += message;
|
||||||
const messages = Array.from(document.body.querySelectorAll('#mini-mid'));
|
const messages = Array.from(document.body.querySelectorAll('#mini-mid'));
|
||||||
const lastMessage = messages[messages.length - 1];
|
const lastMessage = messages[messages.length - 1];
|
||||||
lastMessage.scrollIntoView({ behavior: 'smooth' });
|
lastMessage.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|
||||||
showPreviewChatMessage();
|
showPreviewChatMessage();
|
||||||
|
|
||||||
function hideText(button, field) {
|
function hideText(button, field) {
|
||||||
document.body.querySelector(button).addEventListener('click', () => {
|
document.body.querySelector(button).addEventListener('click', () => {
|
||||||
const passwordInput = document.querySelector(field);
|
const passwordInput = document.querySelector(field);
|
||||||
if (passwordInput.type === 'password') {
|
if (passwordInput.type === 'password') {
|
||||||
passwordInput.type = 'lol';
|
passwordInput.type = 'lol';
|
||||||
} else {
|
} else {
|
||||||
passwordInput.type = 'password';
|
passwordInput.type = 'password';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN');
|
hideText('.password-toggle-btn1', '#TWITCH_OAUTH_TOKEN');
|
||||||
|
hideText('.password-toggle-btn2', '#TROVO_OAUTH_TOKEN');
|
||||||
hideText('.password-toggle-btn4', '#AMAZON_ACCESS_KEY');
|
hideText('.password-toggle-btn4', '#AMAZON_ACCESS_KEY');
|
||||||
hideText('.password-toggle-btn5', '#AMAZON_ACCESS_SECRET');
|
hideText('.password-toggle-btn5', '#AMAZON_ACCESS_SECRET');
|
||||||
hideText('.password-toggle-btn6', '#GOOGLE_API_KEY');
|
hideText('.password-toggle-btn6', '#GOOGLE_API_KEY');
|
||||||
|
|
||||||
|
function setZoomLevel(currentZoom, zoomIn) {
|
||||||
|
let newZoom = currentZoom.toFixed(2);
|
||||||
|
|
||||||
|
if (zoomIn === true && currentZoom < 4.95) {
|
||||||
|
newZoom = (currentZoom + 0.05).toFixed(2);
|
||||||
|
}
|
||||||
|
if (zoomIn === false && currentZoom > 0.25) {
|
||||||
|
newZoom = (currentZoom - 0.05).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
webFrame.setZoomFactor(parseFloat(newZoom));
|
||||||
|
settings.GENERAL.ZOOMLEVEL = newZoom;
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
|
document.body.querySelector('#ZOOMLEVEL').value = (settings.GENERAL.ZOOMLEVEL * 100).toFixed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLanguageProperties(languageToDetect) {
|
||||||
|
try {
|
||||||
|
const filteredLanguage = Object.keys(languageObject.languages).reduce(function (accumulator, currentValue) {
|
||||||
|
if (
|
||||||
|
languageObject.languages[currentValue].IETF === languageToDetect ||
|
||||||
|
languageObject.languages[currentValue].ISO639 === languageToDetect ||
|
||||||
|
languageObject.languages[currentValue].ISO3166 === languageToDetect
|
||||||
|
) {
|
||||||
|
accumulator[currentValue] = languageObject.languages[currentValue];
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const language = {
|
||||||
|
name: Object.getOwnPropertyNames(filteredLanguage)[0],
|
||||||
|
ISO3166: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO3166,
|
||||||
|
ISO639: filteredLanguage[Object.keys(filteredLanguage)[0]].ISO639,
|
||||||
|
IETF: filteredLanguage[Object.keys(filteredLanguage)[0]].IETF
|
||||||
|
};
|
||||||
|
|
||||||
|
return language;
|
||||||
|
} catch (e) {
|
||||||
|
// console.error(error);
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let customEmotes = null;
|
||||||
|
if (fs.existsSync(customEmotesListSavePath)) {
|
||||||
|
const file = fs.readFileSync(customEmotesListSavePath);
|
||||||
|
customEmotes = JSON.parse(file);
|
||||||
|
customEmojiList = [...customEmojiList, ...customEmotes];
|
||||||
|
}
|
||||||
|
|
||||||
|
emojiPicker.customEmoji = customEmojiList;
|
||||||
|
|
||||||
|
function saveCustomEmotesToFile(emotes) {
|
||||||
|
const data = JSON.stringify(emotes);
|
||||||
|
customEmojiList = [...customEmojiList, data];
|
||||||
|
const savePath =
|
||||||
|
main.isPackaged === true ? path.join(resourcesPath, './custom-emotes.json') : path.join(resourcesPath, './config/custom-emotes.json');
|
||||||
|
|
||||||
|
fs.writeFile(savePath, data, error => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function countWords(str) {
|
||||||
|
return str.trim().split(/\s+/).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// function saveSettingsToFile(settings) {
|
||||||
|
// const data = JSON.stringify(settings);
|
||||||
|
// const savePath =
|
||||||
|
// main.isPackaged === true ? path.join(resourcesPath, './settings.json') : path.join(resourcesPath, './config/settings.json');
|
||||||
|
|
||||||
|
// fs.writeFile(savePath, data, error => {
|
||||||
|
// if (error) {
|
||||||
|
// console.error(error);
|
||||||
|
// throw error;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// var videoElement = document.querySelector('video');
|
||||||
|
// var audioSelect = document.querySelector('select#audioSource');
|
||||||
|
// var videoSelect = document.querySelector('select#videoSource');
|
||||||
|
|
||||||
|
// audioSelect.onchange = getStream;
|
||||||
|
// videoSelect.onchange = getStream;
|
||||||
|
|
||||||
|
// getStream().then(getDevices).then(gotDevices);
|
||||||
|
|
||||||
|
// function getDevices() {
|
||||||
|
// // AFAICT in Safari this only gets default devices until gUM is called :/
|
||||||
|
// return navigator.mediaDevices.enumerateDevices();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function gotDevices(deviceInfos) {
|
||||||
|
// window.deviceInfos = deviceInfos; // make available to console
|
||||||
|
// console.log('Available input and output devices:', deviceInfos);
|
||||||
|
// for (const deviceInfo of deviceInfos) {
|
||||||
|
// const option = document.createElement('option');
|
||||||
|
// option.value = deviceInfo.deviceId;
|
||||||
|
// if (deviceInfo.kind === 'audioinput') {
|
||||||
|
// option.text = deviceInfo.label || `Microphone ${audioSelect.length + 1}`;
|
||||||
|
// audioSelect.appendChild(option);
|
||||||
|
// } else if (deviceInfo.kind === 'videoinput') {
|
||||||
|
// option.text = deviceInfo.label || `Camera ${videoSelect.length + 1}`;
|
||||||
|
// videoSelect.appendChild(option);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function getStream() {
|
||||||
|
// if (window.stream) {
|
||||||
|
// window.stream.getTracks().forEach(track => {
|
||||||
|
// track.stop();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// const audioSource = audioSelect.value;
|
||||||
|
// const videoSource = videoSelect.value;
|
||||||
|
// const constraints = {
|
||||||
|
// audio: { deviceId: audioSource ? { exact: audioSource } : undefined },
|
||||||
|
// video: { deviceId: videoSource ? { exact: videoSource } : undefined }
|
||||||
|
// };
|
||||||
|
// return navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function gotStream(stream) {
|
||||||
|
// window.stream = stream; // make stream available to console
|
||||||
|
// audioSelect.selectedIndex = [...audioSelect.options].findIndex(option => option.text === stream.getAudioTracks()[0].label);
|
||||||
|
// videoSelect.selectedIndex = [...videoSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
|
||||||
|
// videoElement.srcObject = stream;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleError(error) {
|
||||||
|
// console.error('Error: ', error);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const settingxs = {
|
||||||
|
// video: true
|
||||||
|
// };
|
||||||
|
|
||||||
|
// navigator.mediaDevices
|
||||||
|
// .getUserMedia(settingxs)
|
||||||
|
// .then(stream => {
|
||||||
|
// const video = document.getElementById('video');
|
||||||
|
// video.srcObject = stream;
|
||||||
|
// video.play();
|
||||||
|
// })
|
||||||
|
// .catch(err => {
|
||||||
|
// console.log(err);
|
||||||
|
// alert('Não há permissões para acessar a webcam.');
|
||||||
|
// });
|
||||||
|
|
|
||||||
|
|
@ -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!';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
135
src/js/server.js
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global settings */
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
@ -5,78 +7,115 @@ const http = require('http');
|
||||||
const localServer = http.createServer(app);
|
const localServer = http.createServer(app);
|
||||||
const io = require('socket.io')(localServer);
|
const io = require('socket.io')(localServer);
|
||||||
|
|
||||||
let requestCount = 0;
|
const requestCount = 0;
|
||||||
|
|
||||||
function startVtuberModule() {
|
function startVtuberModule() {
|
||||||
if (!settings.MODULES.USE_VTUBER) {
|
if (!settings.MODULES.USE_VTUBER) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
|
app.use('/vtuber', express.static(path.join(__dirname, '../modules/vtuber/')));
|
||||||
|
|
||||||
let vtuber = document.body.querySelector('#BrowsersourceVtuber');
|
const vtuber = document.body.querySelector('#BrowsersourceVtuber');
|
||||||
let vtuberframe = document.createElement('iframe');
|
const vtuberframe = document.createElement('iframe');
|
||||||
vtuberframe.class = 'frame';
|
vtuberframe.class = 'frame';
|
||||||
vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`;
|
vtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/vtuber`;
|
||||||
vtuberframe.style.width = '100%';
|
vtuberframe.style.width = '100%';
|
||||||
vtuberframe.style.height = '100%';
|
vtuberframe.style.height = '100%';
|
||||||
vtuberframe.frameBorder = 0;
|
vtuberframe.frameBorder = 0;
|
||||||
vtuber.appendChild(vtuberframe);
|
vtuber.appendChild(vtuberframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
startVtuberModule();
|
startVtuberModule();
|
||||||
|
|
||||||
|
function startPNGtuberModule() {
|
||||||
|
if (!settings.MODULES.USE_PNGTUBER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/pngtuber', express.static(path.join(__dirname, '../modules/pngtuber/')));
|
||||||
|
|
||||||
|
const pngtuber = document.body.querySelector('#BrowsersourcePNGTuber');
|
||||||
|
const pngtuberframe = document.createElement('iframe');
|
||||||
|
pngtuberframe.class = 'frame';
|
||||||
|
pngtuberframe.src = `http://localhost:${settings.GENERAL.PORT}/pngtuber`;
|
||||||
|
pngtuberframe.style.width = '100%';
|
||||||
|
pngtuberframe.style.height = '100%';
|
||||||
|
pngtuberframe.frameBorder = 0;
|
||||||
|
pngtuber.appendChild(pngtuberframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
startPNGtuberModule();
|
||||||
|
|
||||||
|
function startChatWindowModule() {
|
||||||
|
if (!settings.MODULES.USE_CHATBUBBLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
|
||||||
|
|
||||||
|
const chat = document.body.querySelector('#BrowsersourceChatWindow');
|
||||||
|
const chatframe = document.createElement('iframe');
|
||||||
|
chatframe.class = 'frame';
|
||||||
|
chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`;
|
||||||
|
chatframe.style.width = '100%';
|
||||||
|
chatframe.style.height = '100%';
|
||||||
|
chatframe.frameBorder = 0;
|
||||||
|
chat.appendChild(chatframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
startChatWindowModule();
|
||||||
|
|
||||||
function startChatBubbleModule() {
|
function startChatBubbleModule() {
|
||||||
if (!settings.MODULES.USE_CHATBUBBLE) {
|
if (!settings.MODULES.USE_CHATBUBBLE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use('/chat', express.static(path.join(__dirname, '../modules/chat')));
|
app.use('/chatbubble', express.static(path.join(__dirname, '../modules/chatbubble')));
|
||||||
|
|
||||||
let chat = document.body.querySelector('#BrowsersourceChat');
|
const chatBubble = document.body.querySelector('#BrowsersourceChatBubble');
|
||||||
let chatframe = document.createElement('iframe');
|
const chatBubbleFrame = document.createElement('iframe');
|
||||||
chatframe.class = 'frame';
|
chatBubbleFrame.class = 'frame';
|
||||||
chatframe.src = `http://localhost:${settings.GENERAL.PORT}/chat`;
|
chatBubbleFrame.src = `http://localhost:${settings.GENERAL.PORT}/chatbubble`;
|
||||||
chatframe.style.width = '100%';
|
chatBubbleFrame.style.width = '100%';
|
||||||
chatframe.style.height = '100%';
|
chatBubbleFrame.style.height = '100%';
|
||||||
chatframe.frameBorder = 0;
|
chatBubbleFrame.frameBorder = 0;
|
||||||
chat.appendChild(chatframe);
|
chatBubble.appendChild(chatBubbleFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
startChatBubbleModule();
|
startChatBubbleModule();
|
||||||
|
|
||||||
function startSTT() {}
|
|
||||||
|
|
||||||
// Middleware to conditionally serve routes
|
// Middleware to conditionally serve routes
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') {
|
if (!settings.MODULES.USE_VTUBER && req.path === '/vtuber') {
|
||||||
res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled
|
res.sendStatus(404); // Return a 404 status for /vtuber when it's disabled
|
||||||
} else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') {
|
} else if (!settings.MODULES.USE_CHATBUBBLE && req.path === '/chat') {
|
||||||
res.sendStatus(404); // Return a 404 status for /chat when it's disabled
|
res.sendStatus(404); // Return a 404 status for /chat when it's disabled
|
||||||
} else {
|
} else {
|
||||||
next(); // Proceed to the next middleware or route handler
|
next(); // Proceed to the next middleware or route handler
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
localServer.listen(settings.GENERAL.PORT, () => {
|
localServer.listen(settings.GENERAL.PORT, () => {
|
||||||
startVtuberModule();
|
startVtuberModule();
|
||||||
startChatBubbleModule();
|
startChatWindowModule();
|
||||||
|
startChatBubbleModule();
|
||||||
if (settings.TTS.USE_TTS) {
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle socket connections
|
// Handle socket connections
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', socket => {
|
||||||
// Receive data from the client
|
// Receive data from the client
|
||||||
socket.on('message', (data) => {});
|
socket.on('message', data => {});
|
||||||
|
socket.on('chat-out', message => {
|
||||||
|
socket.broadcast.emit('chat-in', message);
|
||||||
|
});
|
||||||
|
|
||||||
// Receive data from the client
|
// Receive data from the client
|
||||||
socket.on('xxx', (logoUrl, username, message) => {
|
socket.on('xxx', (logoUrl, username, message) => {
|
||||||
socket.broadcast.emit('message', logoUrl, username, message);
|
socket.broadcast.emit('message', logoUrl, username, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {});
|
socket.on('disconnect', () => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { startVtuberModule, startChatBubbleModule };
|
module.exports = { startVtuberModule, startChatBubbleModule, startChatWindowModule };
|
||||||
|
|
|
||||||
209
src/js/sound.js
|
|
@ -1,120 +1,143 @@
|
||||||
let trueMessage = '';
|
/* global ttsAudioFile, main, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */
|
||||||
let currentLogoUrl = '';
|
|
||||||
let currentUsername = '';
|
const voiceSoundArray = [];
|
||||||
let voiceSoundArray = [];
|
|
||||||
let status = 0;
|
let status = 0;
|
||||||
let counter = 0;
|
const counter = 0;
|
||||||
|
|
||||||
const playTTS = (data) =>
|
const playTTS = data =>
|
||||||
new Promise((resolve) => {
|
new Promise(resolve => {
|
||||||
ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`);
|
ttsAudioFile = path.join(
|
||||||
const tts = new Audio(ttsAudioFile);
|
resourcesPath,
|
||||||
console.log(settings.AUDIO.TTS_AUDIO_DEVICE);
|
main.isPackaged ? `./sounds/${data.service}_${data.count}.mp3` : `../sounds/${data.service}_${data.count}.mp3`
|
||||||
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
|
);
|
||||||
|
const tts = new Audio(ttsAudioFile);
|
||||||
|
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
|
||||||
|
|
||||||
tts.addEventListener('ended', () => {
|
tts.addEventListener('ended', () => {
|
||||||
console.log('ended');
|
// console.log('ended');
|
||||||
fs.unlink(ttsAudioFile, (err) => {
|
fs.unlink(ttsAudioFile, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('TEST');
|
console.error(err);
|
||||||
|
resolve('finished');
|
||||||
resolve('finished');
|
return;
|
||||||
return;
|
}
|
||||||
}
|
resolve('finished');
|
||||||
resolve('finished');
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
|
|
||||||
.then(() => {
|
|
||||||
console.log('playing');
|
|
||||||
tts.volume = settings.AUDIO.TTS_VOLUME / 100;
|
|
||||||
tts.play().catch((error) => {
|
|
||||||
resolve('finished');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to set audio output device:', error);
|
|
||||||
resolve('finished');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tts
|
||||||
|
.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE)
|
||||||
|
.then(() => {
|
||||||
|
// console.log('playing');
|
||||||
|
tts.volume = settings.AUDIO.TTS_VOLUME / 100;
|
||||||
|
tts.play().catch(error => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
resolve('finished');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to set audio output device:', error);
|
||||||
|
resolve('finished');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function shiftVoice() {
|
async function shiftVoice() {
|
||||||
status = 1;
|
status = 1;
|
||||||
while (voiceSoundArray.length > 0) {
|
while (voiceSoundArray.length > 0) {
|
||||||
await playTTS(voiceSoundArray.shift());
|
await playTTS(voiceSoundArray.shift());
|
||||||
}
|
}
|
||||||
status = 0;
|
status = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(data) {
|
function add(data) {
|
||||||
voiceSoundArray.push(data);
|
voiceSoundArray.push(data);
|
||||||
if (status === 0) {
|
if (status === 0) {
|
||||||
shiftVoice();
|
shiftVoice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playNotificationSound() {
|
function playNotificationSound() {
|
||||||
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
|
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
|
||||||
let notfication = new Audio(
|
const notfication = new Audio(
|
||||||
path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`),
|
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.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100;
|
||||||
notfication.play();
|
notfication.play().catch(error => {
|
||||||
}
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to set audio output device:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play sound function
|
// Play sound function
|
||||||
function playAudio(data) {
|
function playAudio(data) {
|
||||||
if (data.service !== '') {
|
if (data.service !== '') {
|
||||||
add(data);
|
add(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playVoice(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;
|
||||||
textObject.filtered = `${username}: ${filteredMessage}`;
|
textObject.filtered = `${message.username}: ${message.filteredMessage}`;
|
||||||
|
|
||||||
// if (
|
if (
|
||||||
// settings.TTS.PRIMARY_TTS_LANGUAGE.toLowerCase() !== settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase() &&
|
settings.LANGUAGE.USE_DETECTION &&
|
||||||
// language[0].lang === settings.TTS.SECONDARY_TTS_LANGUAGE.toLowerCase()
|
settings.TTS.SECONDARY_VOICE &&
|
||||||
// ) {
|
message.filteredMessage.length > 20 &&
|
||||||
// voice = settings.TTS.SECONDARY_TTS_NAME;
|
countWords(message.filteredMessage) > 4
|
||||||
// textObject.filtered = `${username}: ${filteredMessage}`;
|
) {
|
||||||
// } else {
|
const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE);
|
||||||
// voice = settings.TTS.PRIMARY_TTS_NAME;
|
if (message.language.detectedLanguage === null || message.language.detectedLanguage.ISO639 === secondaryTTSLanguage.ISO639) {
|
||||||
// textObject.filtered = `${username}: ${filteredMessage}`;
|
voice = settings.TTS.SECONDARY_VOICE;
|
||||||
// }
|
textObject.filtered = message.originalMessage ? message.originalMessage : message.filteredMessage;
|
||||||
|
|
||||||
const service = document.getElementById('primaryTTSService').value;
|
|
||||||
|
|
||||||
switch (service) {
|
|
||||||
case 'Internal':
|
|
||||||
const requestData = {
|
|
||||||
message: textObject.filtered,
|
|
||||||
voice: settings.TTS.PRIMARY_VOICE,
|
|
||||||
};
|
|
||||||
|
|
||||||
let count = await backend.getInternalTTSAudio(requestData);
|
|
||||||
playAudio({ service, message: textObject, count });
|
|
||||||
break;
|
|
||||||
case 'Amazon':
|
|
||||||
// playAudio({ service: 'Amazon', message: textObject, count });
|
|
||||||
break;
|
|
||||||
case 'Google':
|
|
||||||
// playAudio({ service: 'Google', message: textObject, count });
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.MODULES.USE_CHATBUBBLE) {
|
const service = document.getElementById('primaryTTSService').value;
|
||||||
socket.emit('xxx', currentLogoUrl, currentUsername, textObject);
|
|
||||||
|
switch (service) {
|
||||||
|
case 'Internal': {
|
||||||
|
const requestData = {
|
||||||
|
message: textObject.filtered,
|
||||||
|
voice: voice
|
||||||
|
};
|
||||||
|
|
||||||
|
const count = await backend.getInternalTTSAudio(requestData);
|
||||||
|
playAudio({ service, message: textObject, count });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case 'Amazon':
|
||||||
|
// playAudio({ service: 'Amazon', message: textObject, count });
|
||||||
|
break;
|
||||||
|
case 'Google':
|
||||||
|
// playAudio({ service: 'Google', message: textObject, count });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
playNotificationSound();
|
if (settings.MODULES.USE_CHATBUBBLE) {
|
||||||
|
socket.emit('xxx', message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { playAudio, playVoice, playNotificationSound };
|
module.exports = { playAudio, playVoice, playNotificationSound };
|
||||||
|
|
|
||||||
156
src/js/theme.js
|
|
@ -1,145 +1,143 @@
|
||||||
|
/* global settings, root, fs, settingsPath, ini */
|
||||||
|
|
||||||
function changeColor(section, setting, tempSection) {
|
function changeColor(section, setting, tempSection) {
|
||||||
document.querySelector(section).value = setting;
|
document.querySelector(section).value = setting;
|
||||||
const value = document.querySelector(section).value;
|
const value = document.querySelector(section).value;
|
||||||
root.style.setProperty(tempSection, value);
|
root.style.setProperty(tempSection, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentTheme(adjustTemp = false) {
|
function setCurrentTheme(adjustTemp = false) {
|
||||||
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1');
|
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, adjustTemp ? '--main-color1-temp' : '--main-color1');
|
||||||
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2');
|
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, adjustTemp ? '--main-color2-temp' : '--main-color2');
|
||||||
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3');
|
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, adjustTemp ? '--main-color3-temp' : '--main-color3');
|
||||||
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4');
|
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, adjustTemp ? '--main-color4-temp' : '--main-color4');
|
||||||
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar');
|
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, adjustTemp ? '--top-bar-temp' : '--top-bar');
|
||||||
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section');
|
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, adjustTemp ? '--mid-section-temp' : '--mid-section');
|
||||||
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble');
|
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, adjustTemp ? '--chat-bubble-temp' : '--chat-bubble');
|
||||||
changeColor(
|
changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header');
|
||||||
'#CHAT_BUBBLE_HEADER',
|
changeColor(
|
||||||
settings.THEME.CHAT_BUBBLE_HEADER,
|
'#CHAT_BUBBLE_MESSAGE',
|
||||||
adjustTemp ? '--chat-bubble-header-temp' : '--chat-bubble-header',
|
settings.THEME.CHAT_BUBBLE_MESSAGE,
|
||||||
);
|
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message'
|
||||||
changeColor(
|
);
|
||||||
'#CHAT_BUBBLE_MESSAGE',
|
|
||||||
settings.THEME.CHAT_BUBBLE_MESSAGE,
|
|
||||||
adjustTemp ? '--chat-bubble-message-temp' : '--chat-bubble-message',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTheme(true);
|
setCurrentTheme(true);
|
||||||
|
|
||||||
function setTheme() {
|
function setTheme() {
|
||||||
if (settings.THEME.USE_CUSTOM_THEME) {
|
if (settings.THEME.USE_CUSTOM_THEME) {
|
||||||
setCurrentTheme();
|
setCurrentTheme();
|
||||||
} else {
|
} else {
|
||||||
root.style.setProperty('--main-color1', '#6e2c8c');
|
root.style.setProperty('--main-color1', '#6e2c8c');
|
||||||
root.style.setProperty('--main-color2', 'white');
|
root.style.setProperty('--main-color2', 'white');
|
||||||
root.style.setProperty('--main-color3', '#211E1E');
|
root.style.setProperty('--main-color3', '#211E1E');
|
||||||
root.style.setProperty('--main-color4', '#2f2c34');
|
root.style.setProperty('--main-color4', '#2f2c34');
|
||||||
root.style.setProperty('--top-bar', '#100B12');
|
root.style.setProperty('--top-bar', '#100B12');
|
||||||
root.style.setProperty('--mid-section', '#352d3d');
|
root.style.setProperty('--mid-section', '#352d3d');
|
||||||
root.style.setProperty('--chat-bubble', ' #7A6D7F');
|
root.style.setProperty('--chat-bubble', ' #7A6D7F');
|
||||||
root.style.setProperty('--chat-bubble-header', '#141414');
|
root.style.setProperty('--chat-bubble-header', '#141414');
|
||||||
root.style.setProperty('--chat-bubble-message', 'white');
|
root.style.setProperty('--chat-bubble-message', 'white');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_1').addEventListener('input', () => {
|
document.body.querySelector('#MAIN_COLOR_1').addEventListener('input', () => {
|
||||||
const x = document.getElementById('MAIN_COLOR_1').value;
|
const x = document.getElementById('MAIN_COLOR_1').value;
|
||||||
root.style.setProperty('--main-color1-temp', x);
|
root.style.setProperty('--main-color1-temp', x);
|
||||||
console.log(x);
|
console.log(x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => {
|
document.body.querySelector('#MAIN_COLOR_1').addEventListener('change', () => {
|
||||||
settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value;
|
settings.THEME.MAIN_COLOR_1 = document.getElementById('MAIN_COLOR_1').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1');
|
changeColor('#MAIN_COLOR_1', settings.THEME.MAIN_COLOR_1, '--main-color1');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_2').addEventListener('input', () => {
|
document.body.querySelector('#MAIN_COLOR_2').addEventListener('input', () => {
|
||||||
const x = document.getElementById('MAIN_COLOR_2').value;
|
const x = document.getElementById('MAIN_COLOR_2').value;
|
||||||
root.style.setProperty('--main-color2-temp', x);
|
root.style.setProperty('--main-color2-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_2').addEventListener('change', () => {
|
document.body.querySelector('#MAIN_COLOR_2').addEventListener('change', () => {
|
||||||
settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value;
|
settings.THEME.MAIN_COLOR_2 = document.getElementById('MAIN_COLOR_2').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2');
|
changeColor('#MAIN_COLOR_2', settings.THEME.MAIN_COLOR_2, '--main-color2');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_3').addEventListener('input', () => {
|
document.body.querySelector('#MAIN_COLOR_3').addEventListener('input', () => {
|
||||||
const x = document.getElementById('MAIN_COLOR_3').value;
|
const x = document.getElementById('MAIN_COLOR_3').value;
|
||||||
root.style.setProperty('--main-color3-temp', x);
|
root.style.setProperty('--main-color3-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_3').addEventListener('change', () => {
|
document.body.querySelector('#MAIN_COLOR_3').addEventListener('change', () => {
|
||||||
settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value;
|
settings.THEME.MAIN_COLOR_3 = document.getElementById('MAIN_COLOR_3').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3');
|
changeColor('#MAIN_COLOR_3', settings.THEME.MAIN_COLOR_3, '--main-color3');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_4').addEventListener('input', () => {
|
document.body.querySelector('#MAIN_COLOR_4').addEventListener('input', () => {
|
||||||
const x = document.getElementById('MAIN_COLOR_4').value;
|
const x = document.getElementById('MAIN_COLOR_4').value;
|
||||||
root.style.setProperty('--main-color4-temp', x);
|
root.style.setProperty('--main-color4-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MAIN_COLOR_4').addEventListener('change', () => {
|
document.body.querySelector('#MAIN_COLOR_4').addEventListener('change', () => {
|
||||||
settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value;
|
settings.THEME.MAIN_COLOR_4 = document.getElementById('MAIN_COLOR_4').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4');
|
changeColor('#MAIN_COLOR_4', settings.THEME.MAIN_COLOR_4, '--main-color4');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#TOP_BAR').addEventListener('input', () => {
|
document.body.querySelector('#TOP_BAR').addEventListener('input', () => {
|
||||||
const x = document.getElementById('TOP_BAR').value;
|
const x = document.getElementById('TOP_BAR').value;
|
||||||
root.style.setProperty('--top-bar-temp', x);
|
root.style.setProperty('--top-bar-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#TOP_BAR').addEventListener('change', () => {
|
document.body.querySelector('#TOP_BAR').addEventListener('change', () => {
|
||||||
settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value;
|
settings.THEME.TOP_BAR = document.getElementById('TOP_BAR').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar');
|
changeColor('#TOP_BAR', settings.THEME.TOP_BAR, '--top-bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MID_SECTION').addEventListener('input', () => {
|
document.body.querySelector('#MID_SECTION').addEventListener('input', () => {
|
||||||
const x = document.getElementById('MID_SECTION').value;
|
const x = document.getElementById('MID_SECTION').value;
|
||||||
root.style.setProperty('--mid-section-temp', x);
|
root.style.setProperty('--mid-section-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#MID_SECTION').addEventListener('change', () => {
|
document.body.querySelector('#MID_SECTION').addEventListener('change', () => {
|
||||||
settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value;
|
settings.THEME.MID_SECTION = document.getElementById('MID_SECTION').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section');
|
changeColor('#MID_SECTION', settings.THEME.MID_SECTION, '--mid-section');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('input', () => {
|
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('input', () => {
|
||||||
const x = document.getElementById('CHAT_BUBBLE_BG').value;
|
const x = document.getElementById('CHAT_BUBBLE_BG').value;
|
||||||
root.style.setProperty('--chat-bubble-temp', x);
|
root.style.setProperty('--chat-bubble-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('change', () => {
|
document.body.querySelector('#CHAT_BUBBLE_BG').addEventListener('change', () => {
|
||||||
settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value;
|
settings.THEME.CHAT_BUBBLE_BG = document.getElementById('CHAT_BUBBLE_BG').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble');
|
changeColor('#CHAT_BUBBLE_BG', settings.THEME.CHAT_BUBBLE_BG, '--chat-bubble');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('input', () => {
|
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('input', () => {
|
||||||
const x = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
const x = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
||||||
root.style.setProperty('--chat-bubble-header-temp', x);
|
root.style.setProperty('--chat-bubble-header-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('change', () => {
|
document.body.querySelector('#CHAT_BUBBLE_HEADER').addEventListener('change', () => {
|
||||||
settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
settings.THEME.CHAT_BUBBLE_HEADER = document.getElementById('CHAT_BUBBLE_HEADER').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header');
|
changeColor('#CHAT_BUBBLE_HEADER', settings.THEME.CHAT_BUBBLE_HEADER, '--chat-bubble-header');
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('input', () => {
|
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('input', () => {
|
||||||
const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
const x = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
||||||
root.style.setProperty('--chat-bubble-message-temp', x);
|
root.style.setProperty('--chat-bubble-message-temp', x);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('change', () => {
|
document.body.querySelector('#CHAT_BUBBLE_MESSAGE').addEventListener('change', () => {
|
||||||
settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
settings.THEME.CHAT_BUBBLE_MESSAGE = document.getElementById('CHAT_BUBBLE_MESSAGE').value;
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message');
|
changeColor('#CHAT_BUBBLE_MESSAGE', settings.THEME.CHAT_BUBBLE_MESSAGE, '--chat-bubble-message');
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { setTheme };
|
module.exports = { setTheme };
|
||||||
|
|
|
||||||
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 };
|
||||||
612
src/js/twitch.js
|
|
@ -1,147 +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');
|
|
||||||
|
|
||||||
let client;
|
const tmi = require('tmi.js');
|
||||||
|
|
||||||
|
let client = null;
|
||||||
|
let logoUrl = null;
|
||||||
|
const twitchChannels = [];
|
||||||
|
|
||||||
function sendMessage(message) {
|
function sendMessage(message) {
|
||||||
client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error);
|
client.say(settings.TWITCH.CHANNEL_NAME, message).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
client = new tmi.Client({
|
if (settings.TWITCH.USERNAME && settings.TWITCH.OAUTH_TOKEN) {
|
||||||
|
client = new tmi.Client({
|
||||||
options: {
|
options: {
|
||||||
skipUpdatingEmotesets: true,
|
skipUpdatingEmotesets: true
|
||||||
},
|
},
|
||||||
identity: {
|
identity: {
|
||||||
username: settings.TWITCH.USERNAME,
|
username: settings.TWITCH.USERNAME,
|
||||||
password: settings.TWITCH.OAUTH_TOKEN,
|
password: settings.TWITCH.OAUTH_TOKEN
|
||||||
},
|
},
|
||||||
channels: [settings.TWITCH.CHANNEL_NAME],
|
channels: [settings.TWITCH.CHANNEL_NAME]
|
||||||
});
|
});
|
||||||
|
|
||||||
client
|
client
|
||||||
.connect()
|
.connect()
|
||||||
.then((data) => {})
|
.then(data => {})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
|
|
||||||
function ping(element) {
|
client.on('message', (channel, tags, message, self) => {
|
||||||
let value = document.body.querySelector(element);
|
if (self || tags['display-name'] === settings.TWITCH.USERNAME) {
|
||||||
|
return;
|
||||||
client
|
|
||||||
.ping()
|
|
||||||
.then((data) => {
|
|
||||||
value.classList.add('success');
|
|
||||||
value.innerText = 'Success!';
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
value.classList.add('error');
|
|
||||||
value.innerText = 'Failed!';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayTwitchMessage(logoUrl, username, messageObject, fileteredMessage) {
|
|
||||||
const article = document.createElement('article');
|
|
||||||
article.className = 'msg-container msg-remote';
|
|
||||||
|
|
||||||
article.innerHTML = messageTemplates.twitchTemplate;
|
|
||||||
|
|
||||||
const userImg = article.querySelector('.icon-container > .user-img');
|
|
||||||
if (userImg) {
|
|
||||||
userImg.src = logoUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usernameHtml = article.querySelector('.username');
|
|
||||||
if (usernameHtml) {
|
|
||||||
usernameHtml.innerText = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
const postTime = document.createElement('span');
|
|
||||||
postTime.classList.add('post-time');
|
|
||||||
|
|
||||||
if (postTime) {
|
|
||||||
postTime.innerText = getPostTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconContainer = article.querySelector('.icon-container');
|
|
||||||
iconContainer.appendChild(postTime);
|
|
||||||
|
|
||||||
const msg = article.querySelector('.msg-box');
|
|
||||||
if (msg) {
|
|
||||||
messageObject.forEach((entry) => {
|
|
||||||
if (entry.text) {
|
|
||||||
msg.innerHTML += entry.text;
|
|
||||||
} else {
|
|
||||||
msg.innerHTML += entry.html;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// msg.appendChild(postTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the message to the main chat box (shows the message)
|
|
||||||
showChatMessage(article, false);
|
|
||||||
|
|
||||||
if (fileteredMessage) {
|
|
||||||
sound.playVoice(fileteredMessage, logoUrl, username, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.article = article;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProfileImage(userid, username, message, fileteredMessage) {
|
|
||||||
// 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) => {
|
|
||||||
const logoUrl = responseLogoUrl.data.data[0].profile_image_url;
|
|
||||||
displayTwitchMessage(logoUrl, username, message, fileteredMessage);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseString(inputString) {
|
|
||||||
const regex = /(<img.*?\/>)|([^<]+)/g;
|
|
||||||
const matches = inputString.match(regex) || [];
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < matches.length; i++) {
|
|
||||||
const match = matches[i].trim();
|
|
||||||
if (match.startsWith('<img')) {
|
|
||||||
result.push({ html: match });
|
|
||||||
}
|
|
||||||
if (match !== '' && !match.startsWith('<img')) {
|
|
||||||
result.push({ text: match });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on('message', (channel, tags, message, self) => {
|
|
||||||
if (self) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const emotes = tags.emotes || {};
|
const emotes = tags.emotes || {};
|
||||||
const emoteValues = Object.entries(emotes);
|
const emoteValues = Object.entries(emotes);
|
||||||
let fileteredMessage = message;
|
let filteredMessage = message;
|
||||||
let emoteMessage = message;
|
let emoteMessage = message;
|
||||||
|
|
||||||
emoteValues.forEach((entry) => {
|
emoteValues.forEach(entry => {
|
||||||
entry[1].forEach((lol) => {
|
entry[1].forEach(lol => {
|
||||||
const [start, end] = lol.split('-');
|
const [start, end] = lol.split('-');
|
||||||
let emote = `<img src="https://static-cdn.jtvnw.net/emoticons/v2/${entry[0]}/default/dark/1.0"/>`;
|
const emote = `<img src="https://static-cdn.jtvnw.net/emoticons/v2/${entry[0]}/default/dark/1.0"/>`;
|
||||||
emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote);
|
emoteMessage = emoteMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), emote);
|
||||||
fileteredMessage = fileteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
|
filteredMessage = filteredMessage.replaceAll(message.slice(parseInt(start), parseInt(end) + 1), '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let messageObject = parseString(emoteMessage);
|
const messageObject = parseString(emoteMessage);
|
||||||
getProfileImage(tags['user-id'], tags['display-name'], messageObject, fileteredMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = { sendMessage, ping, client };
|
getProfileImage(tags['user-id'], tags['display-name'], messageObject, filteredMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfTokenIsValid() {
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://id.twitch.tv/oauth2/validate',
|
||||||
|
headers: {
|
||||||
|
Authorization: `OAuth ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(response => {
|
||||||
|
document.getElementById('TWITCH_CHANNEL_NAME').disabled = false;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
document.getElementById('TWITCH_CHANNEL_NAME').disabled = true;
|
||||||
|
config.createNotification('Oauth Token is invalid, please obtain a new one', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(checkIfTokenIsValid, 600000);
|
||||||
|
|
||||||
|
if (settings.TWITCH.OAUTH_TOKEN) {
|
||||||
|
checkIfTokenIsValid();
|
||||||
|
} else {
|
||||||
|
document.getElementById('TWITCH_CHANNEL_NAME').disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ping(element) {
|
||||||
|
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) {
|
||||||
|
const regex = /(<img.*?\/>)|([^<]+)/g;
|
||||||
|
const matches = inputString.match(regex) || [];
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
const match = matches[i].trim();
|
||||||
|
if (match.startsWith('<img')) {
|
||||||
|
result.push({ html: match });
|
||||||
|
}
|
||||||
|
if (match !== '' && !match.startsWith('<img')) {
|
||||||
|
result.push({ text: match });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTwitchEmotes(channel) {
|
||||||
|
if (channel.emotes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.emotes.forEach(emote => {
|
||||||
|
if (channel.name !== 'Twitch Global' && emote.emote_type === 'bitstier') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (channel.name !== 'Twitch Global' && emote.emote_type === 'subscriptions' && parseInt(channel.tier) < parseInt(emote.tier)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (channel.name !== 'Twitch Global' && emote.emote_type === 'follower ' && parseInt(channel.tier) === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emojiToBeAdded = {
|
||||||
|
name: emote.name,
|
||||||
|
shortcodes: [emote.name],
|
||||||
|
url: emote.images.url_1x,
|
||||||
|
category: 'Twitch ' + channel.broadcaster_name
|
||||||
|
};
|
||||||
|
customEmojis.push(emojiToBeAdded);
|
||||||
|
});
|
||||||
|
customEmojiList = [...customEmojis];
|
||||||
|
emojiPicker.customEmoji = customEmojiList;
|
||||||
|
saveCustomEmotesToFile(customEmojis);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBetterTtvEmotes(data) {
|
||||||
|
if (data.emotes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.emotes.forEach(emote => {
|
||||||
|
const emojiToBeAdded = {
|
||||||
|
name: emote.code,
|
||||||
|
shortcodes: [emote.code],
|
||||||
|
url: `https://cdn.betterttv.net/emote/${emote.id}/1x.webp`,
|
||||||
|
category: data.name
|
||||||
|
};
|
||||||
|
customEmojis.push(emojiToBeAdded);
|
||||||
|
});
|
||||||
|
customEmojiList = [...customEmojis];
|
||||||
|
emojiPicker.customEmoji = customEmojiList;
|
||||||
|
saveCustomEmotesToFile(customEmojis);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBetterTtvGLobalEmotes() {
|
||||||
|
// Get user Logo with access token
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.betterttv.net/3/cached/emotes/global',
|
||||||
|
headers: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(response => {
|
||||||
|
formatBetterTtvEmotes({ name: 'BetterTTV Global', emotes: response.data });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBetterTtvChannelsEmotes() {
|
||||||
|
betterTTVAutocomplete.tokens.forEach(channel => {
|
||||||
|
getTwitchChannelId(channel.value).then(channelId => {
|
||||||
|
getBetterTtvChannelEmotes(channelId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBetterTtvChannelEmotes(channel) {
|
||||||
|
// Get user Logo with access token
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://api.betterttv.net/3/cached/users/twitch/${channel}`,
|
||||||
|
headers: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(response => {
|
||||||
|
formatBetterTtvEmotes({ name: 'BetterTTV Channels', emotes: [...response.data.channelEmotes, ...response.data.sharedEmotes] });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTwitchUserFollows(paginationToken) {
|
||||||
|
let url = '';
|
||||||
|
if (!paginationToken) {
|
||||||
|
url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&first=100`;
|
||||||
|
} else {
|
||||||
|
url = `https://api.twitch.tv/helix/channels/followed?user_id=${settings.TWITCH.USER_ID}&after=${paginationToken}`;
|
||||||
|
}
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: url,
|
||||||
|
headers: {
|
||||||
|
'Client-ID': settings.TWITCH.CLIENT_ID,
|
||||||
|
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(responseLogoUrl => {
|
||||||
|
// console.log(responseLogoUrl);
|
||||||
|
|
||||||
|
responseLogoUrl.data.data.forEach(channel => {
|
||||||
|
twitchChannels.push({
|
||||||
|
broadcaster_id: channel.broadcaster_id,
|
||||||
|
broadcaster_name: channel.broadcaster_name,
|
||||||
|
tier: '0'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(responseLogoUrl.data.pagination).length !== 0) {
|
||||||
|
getTwitchUserFollows(responseLogoUrl.data.pagination.cursor);
|
||||||
|
} else {
|
||||||
|
getTwitchChannelSubscriptions(twitchChannels);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTwitchChannelSubscriptions(channels) {
|
||||||
|
channels.forEach(channel => {
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://api.twitch.tv/helix/subscriptions/user?broadcaster_id=${channel.broadcaster_id}&user_id=${settings.TWITCH.USER_ID}`,
|
||||||
|
headers: {
|
||||||
|
'Client-ID': settings.TWITCH.CLIENT_ID,
|
||||||
|
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.request(options)
|
||||||
|
.then(responseLogoUrl => {
|
||||||
|
const objIndex = twitchChannels.findIndex(obj => obj.broadcaster_id === channel.broadcaster_id);
|
||||||
|
twitchChannels[objIndex].tier = responseLogoUrl.data.data[0].tier;
|
||||||
|
getTwitchChannelEmotes(channel);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response.status !== 404) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTwitchChannelEmotes(channel) {
|
||||||
|
// Get user Logo with access token
|
||||||
|
options = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `https://api.twitch.tv/helix/chat/emotes?broadcaster_id=${channel.broadcaster_id}`,
|
||||||
|
headers: {
|
||||||
|
'Client-ID': settings.TWITCH.CLIENT_ID,
|
||||||
|
Authorization: `Bearer ${settings.TWITCH.OAUTH_TOKEN}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
|
||||||
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
399
src/main.js
|
|
@ -1,210 +1,279 @@
|
||||||
|
/* global pythonPath, a */
|
||||||
|
|
||||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||||
const { writeIniFile } = require('write-ini-file');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const kill = require('kill-process-by-name');
|
const kill = require('kill-process-by-name');
|
||||||
|
|
||||||
const ini = require('ini');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let resourcesPath = __dirname;
|
let resourcesPath = __dirname;
|
||||||
let settingsPath;
|
let settingsPath = null;
|
||||||
|
|
||||||
let settings;
|
let settings;
|
||||||
let window;
|
let window;
|
||||||
|
|
||||||
if (app.isPackaged) {
|
if (app.isPackaged) {
|
||||||
settingsPath = path.join(process.resourcesPath, './settings.ini');
|
settingsPath = path.join(process.resourcesPath, './settings.json');
|
||||||
pythonPath = path.join(process.resourcesPath, './backend');
|
pythonPath = path.join(process.resourcesPath, './backend');
|
||||||
resourcesPath = process.resourcesPath;
|
resourcesPath = process.resourcesPath;
|
||||||
} else {
|
} else {
|
||||||
settingsPath = path.join(resourcesPath, './config/settings.ini');
|
settingsPath = path.join(resourcesPath, './config/settings.json');
|
||||||
pythonPath = path.join(resourcesPath, './backend');
|
pythonPath = path.join(resourcesPath, './backend');
|
||||||
}
|
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
||||||
if (require('electron-squirrel-startup')) {
|
|
||||||
app.quit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
if (!fs.existsSync(settingsPath)) {
|
if (!fs.existsSync(settingsPath)) {
|
||||||
console.log(resourcesPath);
|
settings = await createSettingsFile();
|
||||||
await createIniFile();
|
} else {
|
||||||
} else {
|
const file = fs.readFileSync(settingsPath);
|
||||||
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
settings = JSON.parse(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
window = new BrowserWindow({
|
||||||
|
icon: path.join(__dirname, '/images/icon-512.png'),
|
||||||
|
width: parseInt(settings.GENERAL.WIDTH),
|
||||||
|
height: parseInt(settings.GENERAL.HEIGHT),
|
||||||
|
x: parseInt(settings.GENERAL.POSITION_X),
|
||||||
|
y: parseInt(settings.GENERAL.POSITION_Y),
|
||||||
|
frame: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
enableRemoteModule: true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
window.loadFile(path.join(__dirname, 'index.html'));
|
||||||
|
|
||||||
window = new BrowserWindow({
|
// Create the second window
|
||||||
icon: path.join(__dirname, '/images/icon-512.png'),
|
const secondWindow = new BrowserWindow({
|
||||||
width: parseInt(settings.GENERAL.WIDTH),
|
width: 1920,
|
||||||
height: parseInt(settings.GENERAL.HEIGHT),
|
height: 1080,
|
||||||
x: parseInt(settings.GENERAL.POSITION_X),
|
// parent: window, // Set the parent window
|
||||||
y: parseInt(settings.GENERAL.POSITION_Y),
|
// modal: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
webPreferences: {
|
show: true, // Hide the second window initially if needed
|
||||||
nodeIntegration: true,
|
webPreferences: {
|
||||||
contextIsolation: false,
|
nodeIntegration: true,
|
||||||
enableRemoteModule: true,
|
contextIsolation: false,
|
||||||
},
|
enableRemoteModule: true,
|
||||||
});
|
backgroundThrottling: false
|
||||||
|
|
||||||
window.loadFile(path.join(__dirname, 'index.html'));
|
|
||||||
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
window.webContents.openDevTools();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.on('close', (e) => {
|
// Load second.html into the second window
|
||||||
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program
|
secondWindow.loadFile(path.join(__dirname, './modules/facemask/index.html'));
|
||||||
const bounds = window.getBounds();
|
// secondWindow.webContents.setFrameRate(60);
|
||||||
|
|
||||||
settings.GENERAL.WIDTH = bounds.width;
|
if (!app.isPackaged) {
|
||||||
settings.GENERAL.HEIGHT = bounds.height;
|
window.webContents.openDevTools();
|
||||||
settings.GENERAL.POSITION_X = bounds.x;
|
secondWindow.webContents.openDevTools();
|
||||||
settings.GENERAL.POSITION_Y = bounds.y;
|
}
|
||||||
|
|
||||||
fs.writeFileSync(settingsPath, ini.stringify(settings));
|
window.on('close', e => {
|
||||||
});
|
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); // load newest settings in case anything changed after starting the program
|
||||||
|
const bounds = window.getBounds();
|
||||||
|
|
||||||
|
settings.GENERAL.WIDTH = bounds.width;
|
||||||
|
settings.GENERAL.HEIGHT = bounds.height;
|
||||||
|
settings.GENERAL.POSITION_X = bounds.x;
|
||||||
|
settings.GENERAL.POSITION_Y = bounds.y;
|
||||||
|
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// app.disableHardwareAcceleration();
|
||||||
|
// app.disableDomainBlockingFor3DAPIs();
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createWindow();
|
createWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', (event) => {
|
app.on('window-all-closed', event => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
// kill('loquendoBot_backend');
|
||||||
}
|
app.quit();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
createWindow();
|
createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
window.webContents.send('quit-event');
|
window.webContents.send('quit-event');
|
||||||
|
// kill('loquendoBot_backend');
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('resize-window', (event, width, height) => {
|
ipcMain.on('resize-window', (event, width, height) => {
|
||||||
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
browserWindow.setSize(width, height);
|
browserWindow.setSize(width, height);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('minimize-window', (event) => {
|
ipcMain.on('minimize-window', event => {
|
||||||
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
browserWindow.minimize();
|
browserWindow.minimize();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('maximize-window', (event) => {
|
ipcMain.on('maximize-window', event => {
|
||||||
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
|
||||||
if (!browserWindow.isMaximized()) {
|
if (!browserWindow.isMaximized()) {
|
||||||
browserWindow.maximize();
|
browserWindow.maximize();
|
||||||
} else {
|
} else {
|
||||||
browserWindow.unmaximize();
|
browserWindow.unmaximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('close-window', event => {
|
||||||
|
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
kill('loquendoBot_backend');
|
||||||
|
browserWindow.close();
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('restart', event => {
|
||||||
|
app.relaunch();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('environment', event => {
|
||||||
|
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createSettingsFile() {
|
||||||
|
const settingsx = {
|
||||||
|
GENERAL: {
|
||||||
|
VOICE_ENABLED: true,
|
||||||
|
NOTIFICATION_ENABLED: true,
|
||||||
|
POSITION_X: 0,
|
||||||
|
POSITION_Y: 0,
|
||||||
|
WIDTH: 1024,
|
||||||
|
HEIGHT: 768,
|
||||||
|
LANGUAGE: 'none',
|
||||||
|
LANGUAGE_INDEX: '0',
|
||||||
|
PORT: 9000,
|
||||||
|
VIEWERS_PANEL: false,
|
||||||
|
LOCATION: pythonPath,
|
||||||
|
ZOOMLEVEL: 1
|
||||||
|
},
|
||||||
|
LANGUAGE: {
|
||||||
|
USE_DETECTION: false,
|
||||||
|
TRANSLATE_TO: 'none',
|
||||||
|
LANGUAGE_INDEX: '0',
|
||||||
|
BROADCAST_TRANSLATION: false,
|
||||||
|
OUTPUT_TO_TTS: false,
|
||||||
|
TRANSLATE_TO_INDEX: 0,
|
||||||
|
SEND_TRANSLATION: false,
|
||||||
|
SEND_TRANSLATION_IN: 'none',
|
||||||
|
SEND_TRANSLATION_OUT: 'none'
|
||||||
|
},
|
||||||
|
TTS: {
|
||||||
|
USE_TTS: false,
|
||||||
|
PRIMARY_VOICE: '',
|
||||||
|
PRIMARY_TTS_LANGUAGE: 'none',
|
||||||
|
SECONDARY_VOICE: '',
|
||||||
|
SECONDARY_TTS_LANGUAGE: 'none',
|
||||||
|
PRIMARY_TTS_LANGUAGE_INDEX: 0,
|
||||||
|
SECONDARY_TTS_LANGUAGE_INDEX: 0
|
||||||
|
},
|
||||||
|
STT: {
|
||||||
|
USE_STT: false,
|
||||||
|
MICROPHONE_ID: 'default',
|
||||||
|
SELECTED_MICROPHONE: 'default',
|
||||||
|
MICROPHONE: 0,
|
||||||
|
LANGUAGE: ''
|
||||||
|
},
|
||||||
|
AUDIO: {
|
||||||
|
USE_NOTIFICATION_SOUNDS: false,
|
||||||
|
SELECTED_NOTIFICATION_AUDIO_DEVICE: 'default',
|
||||||
|
NOTIFICATION_AUDIO_DEVICE: 0,
|
||||||
|
NOTIFICATION_SOUND: 0,
|
||||||
|
NOTIFICATION_VOLUME: 50,
|
||||||
|
SELECTED_TTS_AUDIO_DEVICE: 0,
|
||||||
|
TTS_AUDIO_DEVICE: 'default',
|
||||||
|
TTS_VOLUME: 50
|
||||||
|
},
|
||||||
|
THEME: {
|
||||||
|
USE_CUSTOM_THEME: false,
|
||||||
|
MAIN_COLOR_1: '#cdc1c1',
|
||||||
|
MAIN_COLOR_2: '#b12020',
|
||||||
|
MAIN_COLOR_3: '#6c4104',
|
||||||
|
MAIN_COLOR_4: '#532d2d',
|
||||||
|
TOP_BAR: '#c8ff00',
|
||||||
|
MID_SECTION: '#6b8578',
|
||||||
|
CHAT_BUBBLE_BG: '#447466',
|
||||||
|
CHAT_BUBBLE_HEADER: '#ffffff',
|
||||||
|
CHAT_BUBBLE_MESSAGE: '#b5b5b5'
|
||||||
|
},
|
||||||
|
TWITCH: {
|
||||||
|
USE_TWITCH: false,
|
||||||
|
SEND_CHAT: false,
|
||||||
|
CHANNEL_NAME: '',
|
||||||
|
CHANNEL_USER_ID: '',
|
||||||
|
USERNAME: '',
|
||||||
|
USER_ID: '',
|
||||||
|
USER_LOGO_URL: '',
|
||||||
|
OAUTH_TOKEN: '',
|
||||||
|
BETTERTTV_CHANNELS: [
|
||||||
|
{ value: 'turtlemaw', text: 'turtlemaw' },
|
||||||
|
{ value: 'tO_Ot', text: 'tO_Ot' },
|
||||||
|
{ value: 'adew', text: 'adew' }
|
||||||
|
],
|
||||||
|
CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9'
|
||||||
|
},
|
||||||
|
TROVO: {
|
||||||
|
USE_TROVO: false,
|
||||||
|
SEND_CHAT: false,
|
||||||
|
CHANNEL_NAME: '',
|
||||||
|
CHANNEL_ID: '',
|
||||||
|
USERNAME: '',
|
||||||
|
USER_LOGO_URL: '',
|
||||||
|
USER_ID: '',
|
||||||
|
CLIENT_ID: '8d32385a4be4a29e345aedaf23ca772f',
|
||||||
|
OAUTH_TOKEN: ''
|
||||||
|
},
|
||||||
|
YOUTUBE: {
|
||||||
|
USE_YOUTUBE: false,
|
||||||
|
SEND_CHAT: false,
|
||||||
|
CHANNEL_ID: '',
|
||||||
|
CHANNEL_HANDLE: '',
|
||||||
|
LIVE_ID: ''
|
||||||
|
},
|
||||||
|
DLIVE: {
|
||||||
|
USE_DLIVE: false,
|
||||||
|
SEND_CHAT: false,
|
||||||
|
API_KEY: ''
|
||||||
|
},
|
||||||
|
MODULES: {
|
||||||
|
USE_MODULES: false,
|
||||||
|
USE_VTUBER: false,
|
||||||
|
USE_PNGTUBER: false,
|
||||||
|
USE_CHATBUBBLE: false
|
||||||
|
},
|
||||||
|
AMAZON: {
|
||||||
|
USE_AMAZON: false,
|
||||||
|
ACCESS_KEY: '',
|
||||||
|
ACCESS_SECRET: '',
|
||||||
|
PRIMARY_VOICE: '',
|
||||||
|
SECONDARY_VOICE: '',
|
||||||
|
CHARACTERS_USED: 0
|
||||||
|
},
|
||||||
|
GOOGLE: {
|
||||||
|
USE_GOOGLE: false,
|
||||||
|
API_KEY: '',
|
||||||
|
PRIMARY_VOICE: '',
|
||||||
|
SECONDARY_VOICE: '',
|
||||||
|
CHARACTERS_USED: 0
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
ipcMain.on('close-window', (event) => {
|
fs.writeFile(settingsPath, JSON.stringify(settingsx), error => {
|
||||||
const browserWindow = BrowserWindow.fromWebContents(event.sender);
|
if (error) {
|
||||||
kill('loquendoBot_backend');
|
console.error(error);
|
||||||
browserWindow.close();
|
}
|
||||||
app.quit();
|
});
|
||||||
});
|
return settingsx;
|
||||||
|
|
||||||
ipcMain.on('restart', (event) => {
|
|
||||||
app.relaunch();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('environment', (event) => {
|
|
||||||
event.returnValue = { resourcesPath, pythonPath, settingsPath, settings, isPackaged: app.isPackaged };
|
|
||||||
});
|
|
||||||
|
|
||||||
async function createIniFile() {
|
|
||||||
await writeIniFile(settingsPath, {
|
|
||||||
GENERAL: {
|
|
||||||
VOICE_ENABLED: true,
|
|
||||||
NOTIFICATION_ENABLED: true,
|
|
||||||
POSITION_X: 0,
|
|
||||||
POSITION_Y: 0,
|
|
||||||
WIDTH: 1024,
|
|
||||||
HEIGHT: 768,
|
|
||||||
LANGUAGE: 'EN',
|
|
||||||
PORT: 9000,
|
|
||||||
VIEWERS_PANEL: false,
|
|
||||||
LOCATION: pythonPath,
|
|
||||||
},
|
|
||||||
LANGUAGE: {
|
|
||||||
USE_DETECTION: false,
|
|
||||||
},
|
|
||||||
TTS: {
|
|
||||||
USE_TTS: true,
|
|
||||||
PRIMARY_VOICE: '',
|
|
||||||
PRIMARY_TTS_LANGUAGE: 'EN',
|
|
||||||
SECONDARY_VOICE: '',
|
|
||||||
SECONDARY_TTS_LANGUAGE: 'EN',
|
|
||||||
},
|
|
||||||
STT: {
|
|
||||||
USE_STT: false,
|
|
||||||
MICROPHONE_ID: 'default',
|
|
||||||
SELECTED_MICROPHONE: 'default',
|
|
||||||
MICROPHONE: 0,
|
|
||||||
LANGUAGE: 'vosk-model-small-es-0.42',
|
|
||||||
},
|
|
||||||
AUDIO: {
|
|
||||||
USE_NOTIFICATION_SOUNDS: true,
|
|
||||||
NOTIFICATION_AUDIO_DEVICE: 0,
|
|
||||||
NOTIFICATION_SOUND: 0,
|
|
||||||
NOTIFICATION_VOLUME: 50,
|
|
||||||
SELECTED_TTS_AUDIO_DEVICE: 0,
|
|
||||||
TTS_AUDIO_DEVICE: 'default',
|
|
||||||
TTS_VOLUME: 50,
|
|
||||||
},
|
|
||||||
THEME: {
|
|
||||||
USE_CUSTOM_THEME: false,
|
|
||||||
MAIN_COLOR_1: '#cdc1c1',
|
|
||||||
MAIN_COLOR_2: '#b12020',
|
|
||||||
MAIN_COLOR_3: '#6c4104',
|
|
||||||
MAIN_COLOR_4: '#532d2d',
|
|
||||||
TOP_BAR: '#c8ff00',
|
|
||||||
MID_SECTION: '#6b8578',
|
|
||||||
CHAT_BUBBLE_BG: '#447466',
|
|
||||||
CHAT_BUBBLE_HEADER: '#ffffff',
|
|
||||||
CHAT_BUBBLE_MESSAGE: '#b5b5b5',
|
|
||||||
},
|
|
||||||
TWITCH: {
|
|
||||||
USE_TWITCH: false,
|
|
||||||
CHANNEL_NAME: '',
|
|
||||||
USERNAME: '',
|
|
||||||
USER_ID: '',
|
|
||||||
USER_LOGO_URL: '',
|
|
||||||
OAUTH_TOKEN: '',
|
|
||||||
CLIENT_ID: '2t206sj7rvtr1rutob3p627d13jch9',
|
|
||||||
},
|
|
||||||
MODULES: {
|
|
||||||
USE_MODULES: false,
|
|
||||||
USE_VTUBER: false,
|
|
||||||
USE_CHATBUBBLE: false,
|
|
||||||
},
|
|
||||||
AMAZON: {
|
|
||||||
USE_AMAZON: false,
|
|
||||||
ACCESS_KEY: '',
|
|
||||||
ACCESS_SECRET: '',
|
|
||||||
PRIMARY_VOICE: '',
|
|
||||||
SECONDARY_VOICE: '',
|
|
||||||
CHARACTERS_USED: 0,
|
|
||||||
},
|
|
||||||
GOOGLE: {
|
|
||||||
USE_GOOGLE: false,
|
|
||||||
API_KEY: '',
|
|
||||||
PRIMARY_VOICE: '',
|
|
||||||
SECONDARY_VOICE: '',
|
|
||||||
CHARACTERS_USED: 0,
|
|
||||||
},
|
|
||||||
}).then(() => {
|
|
||||||
settings = ini.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
src/modules/chat/images/twitch-icon.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
|
@ -1,23 +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>
|
|
||||||
<video id="camera" autoplay></video>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,171 +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(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 {
|
|
||||||
position: static;
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-window::before {
|
.input-box {
|
||||||
content: '';
|
display: flex;
|
||||||
flex: 1 0 0px;
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.OptionPanel {
|
.userText {
|
||||||
flex: 3;
|
color: var(--chat-bubble-message);
|
||||||
display: none;
|
font-family: Helvetica;
|
||||||
position: absolute;
|
font-size: 16px;
|
||||||
top: 10px;
|
text-align: right;
|
||||||
left: 0;
|
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%;
|
width: 100%;
|
||||||
height: calc(100% - 25px);
|
border-radius: 0px;
|
||||||
background: transparent;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.OptionPanel.show {
|
.chat-bar-collapsible {
|
||||||
display: block;
|
position: fixed;
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
text-align: left;
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
min-width: 125px;
|
|
||||||
hyphens: auto;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
float: right;
|
width: 100%;
|
||||||
overflow-wrap: break-word;
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
width: 100%;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
::-webkit-scrollbar {
|
||||||
position: relative;
|
width: 4px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message::after {
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #4c4c6a;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.chatBox {
|
||||||
content: '';
|
width: 300px;
|
||||||
border: 2px solid #ff80e1;
|
height: 400px;
|
||||||
position: absolute;
|
max-height: 400px;
|
||||||
left: 50%;
|
display: flex;
|
||||||
top: 100%;
|
flex-direction: column;
|
||||||
transform: translateX(-50%) rotate(180deg);
|
overflow: hidden;
|
||||||
border-width: 10px;
|
box-shadow: 0 0 4px var(--main-color4);
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent transparent rgb(255, 128, 225, 0.7) transparent;
|
|
||||||
color: #ff80e1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sender {
|
.chat-window {
|
||||||
color: #ff80e1;
|
flex: auto;
|
||||||
font-size: 14pt;
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After slide changes */
|
||||||
|
|
||||||
|
.toggle:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--main-color2);
|
||||||
|
left: 5px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox checked effect */
|
||||||
|
|
||||||
|
.checkbox:checked + .toggle::after {
|
||||||
|
left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox checked toggle label bg color */
|
||||||
|
|
||||||
|
.checkbox:checked + .toggle {
|
||||||
|
background-color: var(--main-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox vanished */
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small toggle */
|
||||||
|
|
||||||
|
/* toggle in label designing */
|
||||||
|
|
||||||
|
.toggle-small {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--main-color3);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After slide changes */
|
||||||
|
|
||||||
|
.toggle-small:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: white;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox checked effect */
|
||||||
|
|
||||||
|
.checkbox:checked + .toggle-small::after {
|
||||||
|
left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox checked toggle label bg color */
|
||||||
|
|
||||||
|
.checkbox:checked + .toggle-small {
|
||||||
|
background-color: var(--main-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotes {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotes:hover .dark {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fi {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-header {
|
||||||
|
background-color: var(--main-color4);
|
||||||
|
border-radius: 5px;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 10px 0px 5px -5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-message {
|
||||||
|
position: relative;
|
||||||
|
margin: 20px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-message.user {
|
||||||
|
margin: -20px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-icon {
|
||||||
|
position: relative;
|
||||||
|
padding: 0px 0px 0px 0px;
|
||||||
|
margin: -45px 0px 0px -40px;
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-icon {
|
||||||
|
position: relative;
|
||||||
|
top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-icon {
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-icon.user {
|
||||||
|
left: -18px;
|
||||||
|
top: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-flag {
|
||||||
|
left: unset;
|
||||||
|
right: 18px;
|
||||||
|
top: -65px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +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;
|
||||||
|
}
|
||||||
125
src/modules/chatbubble/main.js
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/* global io */
|
||||||
|
|
||||||
|
// Connect to the Socket.IO server
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
|
// Emit a message to the server
|
||||||
|
socket.emit('message', 'Hello, Server!');
|
||||||
|
|
||||||
|
function getPostTime() {
|
||||||
|
const d = new Date();
|
||||||
|
document.body.querySelectorAll('.container').innerHTML = d.getHours();
|
||||||
|
const hours = d.getHours();
|
||||||
|
const minutes = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
|
||||||
|
const time = `${hours}:${minutes}`;
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showChatMessage(article) {
|
||||||
|
const main = document.querySelector('#chatBox');
|
||||||
|
main.appendChild(article);
|
||||||
|
main.scrollTop = main.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
let textStreamContainer;
|
||||||
|
let x;
|
||||||
|
// const totalDuration = 5000; // Total duration in milliseconds
|
||||||
|
// const charactersPerSecond = 20; // Adjust the number of characters to display per second
|
||||||
|
|
||||||
|
// const streamingSpeed = totalDuration / (textToStream.length / charactersPerSecond);
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let messageStream = '';
|
||||||
|
const tempMessageObject = '';
|
||||||
|
const fullMessageLength = 0;
|
||||||
|
|
||||||
|
function getFullMessageLength(text) {
|
||||||
|
let fullMessageLength = 0;
|
||||||
|
text.forEach(element => {
|
||||||
|
if (element.text) {
|
||||||
|
fullMessageLength += element.text.length;
|
||||||
|
}
|
||||||
|
// element.html;
|
||||||
|
fullMessageLength += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return fullMessageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function streamText() {
|
||||||
|
// if (currentIndex < fullMessageLength) {
|
||||||
|
|
||||||
|
// textStreamContainer.innerHTML += messageStream.filtered.charAt(currentIndex);
|
||||||
|
// currentIndex++;
|
||||||
|
// setTimeout(streamText, 50);
|
||||||
|
// }
|
||||||
|
if (currentIndex < messageStream.length) {
|
||||||
|
textStreamContainer.innerHTML += messageStream.charAt(currentIndex);
|
||||||
|
currentIndex++;
|
||||||
|
setTimeout(streamText, 50);
|
||||||
|
} else {
|
||||||
|
currentIndex = 0;
|
||||||
|
x.classList.add('fade-outx');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayTwitchMessage(message) {
|
||||||
|
if (!message.filteredMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.querySelector(':root');
|
||||||
|
root.style.setProperty('--variable', '5s');
|
||||||
|
|
||||||
|
const article = document.createElement('article');
|
||||||
|
x = article;
|
||||||
|
|
||||||
|
article.className = 'msg-container';
|
||||||
|
|
||||||
|
const placeMessage = `
|
||||||
|
<div class="thomas bounce-in">
|
||||||
|
<div class="message"></div>
|
||||||
|
<div class="sender"></div>
|
||||||
|
<div class="speechbubble"></div>
|
||||||
|
<div class="arrow"></div>
|
||||||
|
</div>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
article.innerHTML = placeMessage;
|
||||||
|
const msg = article.querySelector('.message');
|
||||||
|
|
||||||
|
msg.innerHTML = `<div class="sender">${message.username}</div>`; // \n${message}`;
|
||||||
|
|
||||||
|
msg.style.fontSize = '12pt';
|
||||||
|
|
||||||
|
showChatMessage(article);
|
||||||
|
|
||||||
|
const elements = document.getElementsByClassName('msg-container');
|
||||||
|
if (elements.length > 1) {
|
||||||
|
elements[0].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
article.addEventListener('animationend', e => {
|
||||||
|
if (e.animationName === 'fade-outx') {
|
||||||
|
article.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (elements.length > 1) {
|
||||||
|
elements[0].classList.add('fade-outxx');
|
||||||
|
elements[0].addEventListener('animationend', e => {
|
||||||
|
if (e.animationName === 'fade-outxx') {
|
||||||
|
elements[0].remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// fullMessageLength = getFullMessageLength(messageObject);
|
||||||
|
messageStream = message.filteredMessage;
|
||||||
|
textStreamContainer = document.querySelector('.message');
|
||||||
|
streamText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Receive a message from the server
|
||||||
|
socket.on('message', message => {
|
||||||
|
displayTwitchMessage(message);
|
||||||
|
});
|
||||||
BIN
src/modules/facemask/0320 (1)(1).mp4
Normal file
BIN
src/modules/facemask/0320 (1).mp4
Normal file
BIN
src/modules/facemask/0320(2).mp4
Normal file
BIN
src/modules/facemask/Glowing Eyes Overlay Red Eyes.mp4
Normal file
478
src/modules/facemask/detection.js
Normal file
|
|
@ -0,0 +1,478 @@
|
||||||
|
const { FaceLandmarker, HandLandmarker, PoseLandmarker, FilesetResolver, DrawingUtils } = require('@mediapipe/tasks-vision');
|
||||||
|
|
||||||
|
const videoBlendShapes = document.getElementById('video-blend-shapes');
|
||||||
|
|
||||||
|
const videoSelect = document.querySelector('select#videoSource');
|
||||||
|
|
||||||
|
const videoElement = document.getElementById('video');
|
||||||
|
const canvasElement = document.getElementsByClassName('output_canvas')[0];
|
||||||
|
const canvasCtx = canvasElement.getContext('2d');
|
||||||
|
|
||||||
|
let cameraRunning = false;
|
||||||
|
let cameraVisible = true;
|
||||||
|
let cameraFPS = 30;
|
||||||
|
let cameraWidth = 640;
|
||||||
|
let cameraHeight = 480;
|
||||||
|
|
||||||
|
let lastVideoTime = -1;
|
||||||
|
|
||||||
|
const drawingUtils = new DrawingUtils(canvasCtx);
|
||||||
|
|
||||||
|
getDevices().then(gotDevices);
|
||||||
|
|
||||||
|
let detections = [];
|
||||||
|
|
||||||
|
document.body.querySelector('#cameraVisible').addEventListener('click', async () => {
|
||||||
|
cameraVisible = !cameraVisible;
|
||||||
|
if (cameraVisible) {
|
||||||
|
document.body.querySelector('#cameraVisible').innerHTML = 'Hide Camera';
|
||||||
|
document.body.querySelector('#video').style.display = '';
|
||||||
|
} else {
|
||||||
|
document.body.querySelector('#cameraVisible').innerHTML = 'Show Camera';
|
||||||
|
document.body.querySelector('#video').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#cameraRunning').addEventListener('click', async () => {
|
||||||
|
cameraRunning = !cameraRunning;
|
||||||
|
if (cameraRunning) {
|
||||||
|
getStream();
|
||||||
|
document.body.querySelector('#cameraRunning').innerHTML = 'Stop Camera';
|
||||||
|
} else {
|
||||||
|
if (window.stream) {
|
||||||
|
window.stream.getTracks().forEach(track => {
|
||||||
|
track.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.body.querySelector('#cameraRunning').innerHTML = 'Start Camera';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#cameraResolution').addEventListener('change', async e => {
|
||||||
|
cameraWidth = e.target.options[e.target.selectedIndex].getAttribute('width');
|
||||||
|
cameraHeight = e.target.options[e.target.selectedIndex].getAttribute('height');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#cameraFPS').addEventListener('change', async () => {
|
||||||
|
cameraFPS = document.body.querySelector('#cameraFPS').value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------- Face Detection --------------
|
||||||
|
|
||||||
|
let faceDetectionDelegate = 'GPU';
|
||||||
|
let minFaceDetectionConfidence = 0.5;
|
||||||
|
document.getElementById('minFaceDetectionConfidenceValue').innerHTML = minFaceDetectionConfidence;
|
||||||
|
let minFacePresenceConfidence = 0.5;
|
||||||
|
document.getElementById('minFacePresenceConfidenceValue').innerHTML = minFacePresenceConfidence;
|
||||||
|
let minFaceTrackingConfidence = 0.5;
|
||||||
|
document.getElementById('minFaceTrackingConfidenceValue').innerHTML = minFaceTrackingConfidence;
|
||||||
|
let faceTrackingEnabled = false;
|
||||||
|
let hideFace = false;
|
||||||
|
let faceLandmarker;
|
||||||
|
let resultsFace;
|
||||||
|
|
||||||
|
document.body.querySelector('#face').addEventListener('click', async () => {
|
||||||
|
faceTrackingEnabled = !faceTrackingEnabled;
|
||||||
|
if (faceTrackingEnabled) {
|
||||||
|
createFaceLandmarker();
|
||||||
|
document.body.querySelector('#face').innerHTML = 'Disable face detection';
|
||||||
|
} else {
|
||||||
|
faceLandmarker = null;
|
||||||
|
document.body.querySelector('#face').innerHTML = 'Enable face detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#hideFace').addEventListener('click', async () => {
|
||||||
|
hideFace = !hideFace;
|
||||||
|
if (hideFace) {
|
||||||
|
document.body.querySelector('#hideFace').innerHTML = 'Show face detection';
|
||||||
|
} else {
|
||||||
|
document.body.querySelector('#hideFace').innerHTML = 'Hide face detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#faceDetectionDelegate').addEventListener('change', async () => {
|
||||||
|
faceDetectionDelegate = document.getElementById('faceDetectionDelegate').value;
|
||||||
|
if (faceTrackingEnabled) {
|
||||||
|
createFaceLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minFaceDetectionConfidence').addEventListener('change', async () => {
|
||||||
|
minFaceDetectionConfidence = parseInt(document.getElementById('minFaceDetectionConfidence').value);
|
||||||
|
document.getElementById('minFaceDetectionConfidenceValue').innerHTML = minFaceDetectionConfidence;
|
||||||
|
if (faceTrackingEnabled) {
|
||||||
|
createFaceLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minFacePresenceConfidence').addEventListener('change', async () => {
|
||||||
|
minFacePresenceConfidence = parseInt(document.getElementById('minFacePresenceConfidence').value);
|
||||||
|
document.getElementById('minFacePresenceConfidenceValue').innerHTML = minFacePresenceConfidence;
|
||||||
|
if (faceTrackingEnabled) {
|
||||||
|
createFaceLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minFaceTrackingConfidence').addEventListener('change', async () => {
|
||||||
|
minFaceTrackingConfidence = parseInt(document.getElementById('minFaceTrackingConfidence').value);
|
||||||
|
document.getElementById('minFaceTrackingConfidenceValue').innerHTML = minFaceTrackingConfidence;
|
||||||
|
if (faceTrackingEnabled) {
|
||||||
|
createFaceLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------- Hand Detection --------------
|
||||||
|
|
||||||
|
let handDetectionDelegate = 'GPU';
|
||||||
|
let minHandDetectionConfidence = 0.5;
|
||||||
|
document.getElementById('minHandDetectionConfidenceValue').innerHTML = minHandDetectionConfidence;
|
||||||
|
let minHandPresenceConfidence = 0.5;
|
||||||
|
document.getElementById('minHandPresenceConfidenceValue').innerHTML = minHandPresenceConfidence;
|
||||||
|
let minHandTrackingConfidence = 0.5;
|
||||||
|
document.getElementById('minHandTrackingConfidenceValue').innerHTML = minHandTrackingConfidence;
|
||||||
|
let handTrackingEnabled = false;
|
||||||
|
let hideHand = false;
|
||||||
|
let handLandmarker;
|
||||||
|
let resultsHands;
|
||||||
|
|
||||||
|
document.body.querySelector('#hand').addEventListener('click', async () => {
|
||||||
|
handTrackingEnabled = !handTrackingEnabled;
|
||||||
|
if (handTrackingEnabled) {
|
||||||
|
createHandLandmarker();
|
||||||
|
document.body.querySelector('#hand').innerHTML = 'Disable hand detection';
|
||||||
|
} else {
|
||||||
|
handLandmarker = null;
|
||||||
|
document.body.querySelector('#hand').innerHTML = 'Enable hand detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#hideHand').addEventListener('click', async () => {
|
||||||
|
hideHand = !hideHand;
|
||||||
|
if (hideFace) {
|
||||||
|
document.body.querySelector('#hideHand').innerHTML = 'Show hand detection';
|
||||||
|
} else {
|
||||||
|
document.body.querySelector('#hideHand').innerHTML = 'Hide hand detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#handDetectionDelegate').addEventListener('change', async () => {
|
||||||
|
handDetectionDelegate = document.getElementById('handDetectionDelegate').value;
|
||||||
|
if (handTrackingEnabled) {
|
||||||
|
createHandLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minHandDetectionConfidence').addEventListener('change', async () => {
|
||||||
|
minHandDetectionConfidence = parseInt(document.getElementById('minHandDetectionConfidence').value);
|
||||||
|
document.getElementById('minHandDetectionConfidenceValue').innerHTML = minHandDetectionConfidence;
|
||||||
|
if (handTrackingEnabled) {
|
||||||
|
createHandLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minHandPresenceConfidence').addEventListener('change', async () => {
|
||||||
|
minHandPresenceConfidence = parseInt(document.getElementById('minHandPresenceConfidence').value);
|
||||||
|
document.getElementById('minHandPresenceConfidenceValue').innerHTML = minHandPresenceConfidence;
|
||||||
|
if (handTrackingEnabled) {
|
||||||
|
createHandLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minHandTrackingConfidence').addEventListener('change', async () => {
|
||||||
|
minHandTrackingConfidence = parseInt(document.getElementById('minHandTrackingConfidence').value);
|
||||||
|
document.getElementById('minHandTrackingConfidenceValue').innerHTML = minHandTrackingConfidence;
|
||||||
|
if (handTrackingEnabled) {
|
||||||
|
createHandLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------- Pose Detection --------------
|
||||||
|
|
||||||
|
let poseDetectionDelegate = 'GPU';
|
||||||
|
let minPoseDetectionConfidence = 0.5;
|
||||||
|
document.getElementById('minPoseDetectionConfidenceValue').innerHTML = minPoseDetectionConfidence;
|
||||||
|
let minPosePresenceConfidence = 0.5;
|
||||||
|
document.getElementById('minPosePresenceConfidenceValue').innerHTML = minPosePresenceConfidence;
|
||||||
|
let minPoseTrackingConfidence = 0.5;
|
||||||
|
document.getElementById('minPoseTrackingConfidenceValue').innerHTML = minPoseTrackingConfidence;
|
||||||
|
let poseTrackingEnabled = false;
|
||||||
|
let hidepose = false;
|
||||||
|
let poseLandmarker;
|
||||||
|
let resultsPose;
|
||||||
|
|
||||||
|
document.body.querySelector('#pose').addEventListener('click', async () => {
|
||||||
|
poseTrackingEnabled = !poseTrackingEnabled;
|
||||||
|
if (poseTrackingEnabled) {
|
||||||
|
createPoseLandmarker();
|
||||||
|
document.body.querySelector('#pose').innerHTML = 'Disable pose detection';
|
||||||
|
} else {
|
||||||
|
poseLandmarker = null;
|
||||||
|
document.body.querySelector('#pose').innerHTML = 'Enable pose detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#hidePose').addEventListener('click', async () => {
|
||||||
|
hidepose = !hidepose;
|
||||||
|
if (hideFace) {
|
||||||
|
document.body.querySelector('#hidePose').innerHTML = 'Show pose detection';
|
||||||
|
} else {
|
||||||
|
document.body.querySelector('#hidePose').innerHTML = 'Hide pose detection';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#poseDetectionDelegate').addEventListener('change', async () => {
|
||||||
|
poseDetectionDelegate = document.getElementById('faceDetectionDelegate').value;
|
||||||
|
if (poseTrackingEnabled) {
|
||||||
|
createPoseLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minPoseDetectionConfidence').addEventListener('change', async () => {
|
||||||
|
minPoseDetectionConfidence = parseInt(document.getElementById('minPoseDetectionConfidence').value);
|
||||||
|
document.getElementById('minPoseDetectionConfidenceValue').innerHTML = minPoseDetectionConfidence;
|
||||||
|
if (poseTrackingEnabled) {
|
||||||
|
createPoseLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minPosePresenceConfidence').addEventListener('change', async () => {
|
||||||
|
minPosePresenceConfidence = parseInt(document.getElementById('minPosePresenceConfidence').value);
|
||||||
|
document.getElementById('minPosePresenceConfidenceValue').innerHTML = minPosePresenceConfidence;
|
||||||
|
if (poseTrackingEnabled) {
|
||||||
|
createPoseLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.querySelector('#minPoseTrackingConfidence').addEventListener('change', async () => {
|
||||||
|
minPoseTrackingConfidence = parseInt(document.getElementById('minPoseTrackingConfidence').value);
|
||||||
|
document.getElementById('minPoseTrackingConfidenceValue').innerHTML = minPoseTrackingConfidence;
|
||||||
|
if (poseTrackingEnabled) {
|
||||||
|
createPoseLandmarker();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------- Camera --------------
|
||||||
|
|
||||||
|
function getDevices() {
|
||||||
|
// AFAICT in Safari this only gets default devices until gUM is called :/
|
||||||
|
return navigator.mediaDevices.enumerateDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotDevices(deviceInfos) {
|
||||||
|
window.deviceInfos = deviceInfos; // make available to console
|
||||||
|
for (const deviceInfo of deviceInfos) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = deviceInfo.deviceId;
|
||||||
|
if (deviceInfo.kind === 'videoinput') {
|
||||||
|
option.text = deviceInfo.label || `Camera ${videoSelect.length + 1}`;
|
||||||
|
videoSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStream() {
|
||||||
|
if (window.stream) {
|
||||||
|
window.stream.getTracks().forEach(track => {
|
||||||
|
track.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const videoSource = videoSelect.value;
|
||||||
|
const constraints = {
|
||||||
|
video: {
|
||||||
|
deviceId: videoSource ? { exact: videoSource } : undefined,
|
||||||
|
frameRate: { min: 30, ideal: cameraFPS, max: 60 },
|
||||||
|
width: cameraWidth,
|
||||||
|
height: cameraHeight
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotStream(stream) {
|
||||||
|
window.stream = stream; // make stream available to console
|
||||||
|
videoSelect.selectedIndex = [...videoSelect.options].findIndex(option => option.text === stream.getVideoTracks()[0].label);
|
||||||
|
videoElement.srcObject = stream;
|
||||||
|
videoElement.addEventListener('loadeddata', predictWebcam);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(error) {
|
||||||
|
console.error('Error: ', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- Tracking --------------
|
||||||
|
|
||||||
|
async function createFaceLandmarker() {
|
||||||
|
const filesetResolver = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
|
||||||
|
faceLandmarker = await FaceLandmarker.createFromOptions(filesetResolver, {
|
||||||
|
baseOptions: {
|
||||||
|
modelAssetPath: 'face_landmarker_model/face_landmarker.task',
|
||||||
|
delegate: faceDetectionDelegate
|
||||||
|
},
|
||||||
|
minFaceDetectionConfidence: minFaceDetectionConfidence,
|
||||||
|
minFacePresenceConfidence: minFacePresenceConfidence,
|
||||||
|
minTrackingConfidence: minFaceTrackingConfidence,
|
||||||
|
outputFaceBlendshapes: true,
|
||||||
|
runningMode: 'VIDEO',
|
||||||
|
numFaces: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
window.requestAnimationFrame(predictWebcam);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createHandLandmarker = async () => {
|
||||||
|
const vision = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
|
||||||
|
handLandmarker = await HandLandmarker.createFromOptions(vision, {
|
||||||
|
baseOptions: {
|
||||||
|
modelAssetPath: 'hand_landmarker_model/hand_landmarker.task',
|
||||||
|
delegate: handDetectionDelegate
|
||||||
|
},
|
||||||
|
minHandDetectionConfidence: minHandDetectionConfidence,
|
||||||
|
minHandPresenceConfidence: minHandPresenceConfidence,
|
||||||
|
minTrackingConfidence: minHandTrackingConfidence,
|
||||||
|
runningMode: 'VIDEO',
|
||||||
|
numHands: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
window.requestAnimationFrame(predictWebcam);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPoseLandmarker = async () => {
|
||||||
|
const vision = await FilesetResolver.forVisionTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm');
|
||||||
|
poseLandmarker = await PoseLandmarker.createFromOptions(vision, {
|
||||||
|
baseOptions: {
|
||||||
|
modelAssetPath: 'pose_landmarker_model/pose_landmarker_heavy.task',
|
||||||
|
delegate: poseDetectionDelegate
|
||||||
|
},
|
||||||
|
minPoseDetectionConfidence: minPoseDetectionConfidence,
|
||||||
|
minPosePresenceConfidence: minPosePresenceConfidence,
|
||||||
|
minTrackingConfidence: minPoseTrackingConfidence,
|
||||||
|
runningMode: 'VIDEO',
|
||||||
|
numPoses: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
window.requestAnimationFrame(predictWebcam);
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------- Detection --------------
|
||||||
|
|
||||||
|
function mediapipeRunning() {
|
||||||
|
if (faceLandmarker || handLandmarker || poseLandmarker) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function predictWebcam() {
|
||||||
|
if (!mediapipeRunning()) {
|
||||||
|
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hideFace || !hidepose || !hideHand) {
|
||||||
|
canvasElement.width = videoElement.videoWidth;
|
||||||
|
canvasElement.height = videoElement.videoHeight;
|
||||||
|
} else {
|
||||||
|
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's start detecting the stream.
|
||||||
|
const startTimeMs = performance.now();
|
||||||
|
if (lastVideoTime !== videoElement.currentTime) {
|
||||||
|
lastVideoTime = videoElement.currentTime;
|
||||||
|
resultsFace = faceLandmarker ? faceLandmarker.detectForVideo(videoElement, startTimeMs) : null;
|
||||||
|
// resultsHands = handLandmarker ? handLandmarker.detectForVideo(videoElement, startTimeMs) : null;
|
||||||
|
// resultsPose = poseLandmarker ? poseLandmarker.detectForVideo(videoElement, startTimeMs) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faceTrackingEnabled && resultsFace && resultsFace.faceLandmarks) {
|
||||||
|
// console.log(resultsFace);
|
||||||
|
drawDebugFace(resultsFace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handTrackingEnabled && resultsHands && resultsHands.landmarks) {
|
||||||
|
// drawDebugHands(resultsHands);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poseTrackingEnabled && resultsPose && resultsPose.landmarks) {
|
||||||
|
// drawDebugPose(resultsPose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawBlendShapes(videoBlendShapes, resultsFace.faceBlendshapes);
|
||||||
|
|
||||||
|
// canvasCtx.restore();
|
||||||
|
|
||||||
|
// Call this function again to keep predicting when the browser is ready.
|
||||||
|
if (cameraRunning === true) {
|
||||||
|
window.requestAnimationFrame(predictWebcam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDebugFace(results) {
|
||||||
|
if (results) {
|
||||||
|
detections = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideFace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const landmarks of results.faceLandmarks) {
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_TESSELATION, { color: '#C0C0C070', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYE, { color: '#FF3030', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_EYEBROW, { color: '#FF3030', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_EYE, { color: '#30FF30', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_EYEBROW, { color: '#30FF30', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_FACE_OVAL, { color: '#E0E0E0', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LIPS, { color: '#E0E0E0', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_RIGHT_IRIS, { color: '#FF3030', lineWidth: 1 });
|
||||||
|
drawingUtils.drawConnectors(landmarks, FaceLandmarker.FACE_LANDMARKS_LEFT_IRIS, { color: '#30FF30', lineWidth: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDebugHands(results) {
|
||||||
|
if (hideHand) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const landmarks of results.landmarks) {
|
||||||
|
drawingUtils.drawConnectors(landmarks, HandLandmarker.HAND_CONNECTIONS, {
|
||||||
|
color: '#E0E0E0',
|
||||||
|
lineWidth: 1
|
||||||
|
});
|
||||||
|
drawingUtils.drawLandmarks(landmarks, { color: '#E0E0E0', lineWidth: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDebugPose(results) {
|
||||||
|
if (hidePose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const landmark of results.landmarks) {
|
||||||
|
drawingUtils.drawLandmarks(landmark, {
|
||||||
|
radius: data => DrawingUtils.lerp(data.from.z, -0.15, 0.1, 5, 1)
|
||||||
|
});
|
||||||
|
drawingUtils.drawConnectors(landmark, PoseLandmarker.POSE_CONNECTIONS, { color: '#E0E0E0', lineWidth: 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBlendShapes(el, blendShapes) {
|
||||||
|
if (!blendShapes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(blendShapes[0]);
|
||||||
|
|
||||||
|
let htmlMaker = '';
|
||||||
|
blendShapes[0].categories.map(shape => {
|
||||||
|
htmlMaker += `
|
||||||
|
<li class="blend-shapes-item">
|
||||||
|
<span class="blend-shapes-label">${shape.displayName || shape.categoryName}</span>
|
||||||
|
<span class="blend-shapes-value" style="width: calc(${+shape.score * 100}% - 120px)">${(+shape.score).toFixed(4)}</span>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
el.innerHTML = htmlMaker;
|
||||||
|
}
|
||||||
75
src/modules/facemask/drawings/Guard.json
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 15,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 263.0718090188531,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
152,
|
||||||
|
148,
|
||||||
|
176,
|
||||||
|
149,
|
||||||
|
150,
|
||||||
|
136,
|
||||||
|
172,
|
||||||
|
58,
|
||||||
|
132,
|
||||||
|
93,
|
||||||
|
234,
|
||||||
|
127,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
103,
|
||||||
|
67,
|
||||||
|
109,
|
||||||
|
10,
|
||||||
|
338,
|
||||||
|
297,
|
||||||
|
332,
|
||||||
|
284,
|
||||||
|
251,
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
361,
|
||||||
|
288,
|
||||||
|
397,
|
||||||
|
365,
|
||||||
|
379,
|
||||||
|
378,
|
||||||
|
400,
|
||||||
|
377,
|
||||||
|
152
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 116.38012093685805,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 234.7247860532549,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
151,
|
||||||
|
50,
|
||||||
|
280,
|
||||||
|
151,
|
||||||
|
9,
|
||||||
|
330,
|
||||||
|
101,
|
||||||
|
9,
|
||||||
|
151,
|
||||||
|
361
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
448
src/modules/facemask/drawings/clown.json
Normal file
|
|
@ -0,0 +1,448 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 230,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 25,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
14,
|
||||||
|
87,
|
||||||
|
178,
|
||||||
|
88,
|
||||||
|
95,
|
||||||
|
78,
|
||||||
|
191,
|
||||||
|
80,
|
||||||
|
81,
|
||||||
|
82,
|
||||||
|
13,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
0,
|
||||||
|
164,
|
||||||
|
2,
|
||||||
|
94,
|
||||||
|
19,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
195,
|
||||||
|
197,
|
||||||
|
6,
|
||||||
|
122,
|
||||||
|
245,
|
||||||
|
244,
|
||||||
|
243,
|
||||||
|
112,
|
||||||
|
26,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
110,
|
||||||
|
226,
|
||||||
|
247,
|
||||||
|
30,
|
||||||
|
29,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
56,
|
||||||
|
190,
|
||||||
|
243,
|
||||||
|
244,
|
||||||
|
245,
|
||||||
|
122,
|
||||||
|
6,
|
||||||
|
168,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
151,
|
||||||
|
10,
|
||||||
|
109,
|
||||||
|
67,
|
||||||
|
103,
|
||||||
|
54,
|
||||||
|
21,
|
||||||
|
162,
|
||||||
|
127,
|
||||||
|
234,
|
||||||
|
93,
|
||||||
|
132,
|
||||||
|
58,
|
||||||
|
172,
|
||||||
|
136,
|
||||||
|
150,
|
||||||
|
149,
|
||||||
|
176,
|
||||||
|
148,
|
||||||
|
152,
|
||||||
|
175,
|
||||||
|
199,
|
||||||
|
200,
|
||||||
|
18,
|
||||||
|
17,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
14
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 75,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
152,
|
||||||
|
175,
|
||||||
|
199,
|
||||||
|
200,
|
||||||
|
18,
|
||||||
|
17,
|
||||||
|
16,
|
||||||
|
15,
|
||||||
|
14,
|
||||||
|
317,
|
||||||
|
402,
|
||||||
|
318,
|
||||||
|
324,
|
||||||
|
308,
|
||||||
|
415,
|
||||||
|
310,
|
||||||
|
311,
|
||||||
|
312,
|
||||||
|
13,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
0,
|
||||||
|
164,
|
||||||
|
2,
|
||||||
|
94,
|
||||||
|
19,
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
195,
|
||||||
|
197,
|
||||||
|
6,
|
||||||
|
351,
|
||||||
|
465,
|
||||||
|
464,
|
||||||
|
463,
|
||||||
|
341,
|
||||||
|
256,
|
||||||
|
252,
|
||||||
|
253,
|
||||||
|
254,
|
||||||
|
339,
|
||||||
|
446,
|
||||||
|
467,
|
||||||
|
260,
|
||||||
|
259,
|
||||||
|
257,
|
||||||
|
258,
|
||||||
|
286,
|
||||||
|
414,
|
||||||
|
463,
|
||||||
|
464,
|
||||||
|
465,
|
||||||
|
351,
|
||||||
|
6,
|
||||||
|
168,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
151,
|
||||||
|
10,
|
||||||
|
338,
|
||||||
|
297,
|
||||||
|
332,
|
||||||
|
284,
|
||||||
|
251,
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
361,
|
||||||
|
288,
|
||||||
|
397,
|
||||||
|
365,
|
||||||
|
379,
|
||||||
|
378,
|
||||||
|
400,
|
||||||
|
377,
|
||||||
|
152,
|
||||||
|
356
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 115,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
56,
|
||||||
|
190,
|
||||||
|
243,
|
||||||
|
112,
|
||||||
|
26,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
110,
|
||||||
|
226,
|
||||||
|
247,
|
||||||
|
30,
|
||||||
|
29,
|
||||||
|
27,
|
||||||
|
323
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 115,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
257,
|
||||||
|
259,
|
||||||
|
260,
|
||||||
|
467,
|
||||||
|
446,
|
||||||
|
339,
|
||||||
|
254,
|
||||||
|
253,
|
||||||
|
252,
|
||||||
|
256,
|
||||||
|
341,
|
||||||
|
463,
|
||||||
|
414,
|
||||||
|
286,
|
||||||
|
258,
|
||||||
|
257
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 355,
|
||||||
|
"fill_S": 70,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
1,
|
||||||
|
44,
|
||||||
|
220,
|
||||||
|
134,
|
||||||
|
51,
|
||||||
|
5,
|
||||||
|
281,
|
||||||
|
363,
|
||||||
|
440,
|
||||||
|
274,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 355,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 30,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
27,
|
||||||
|
52,
|
||||||
|
29,
|
||||||
|
27
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 355,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 30,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
23,
|
||||||
|
101,
|
||||||
|
24,
|
||||||
|
23
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 320,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 30,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 235,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
257,
|
||||||
|
282,
|
||||||
|
259,
|
||||||
|
257
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 320,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 30,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 235,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
253,
|
||||||
|
330,
|
||||||
|
254,
|
||||||
|
253
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 210,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 170,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
61,
|
||||||
|
76,
|
||||||
|
62,
|
||||||
|
78,
|
||||||
|
95,
|
||||||
|
88,
|
||||||
|
178,
|
||||||
|
87,
|
||||||
|
14,
|
||||||
|
317,
|
||||||
|
402,
|
||||||
|
318,
|
||||||
|
324,
|
||||||
|
308,
|
||||||
|
292,
|
||||||
|
306,
|
||||||
|
291,
|
||||||
|
306,
|
||||||
|
292,
|
||||||
|
308,
|
||||||
|
319,
|
||||||
|
404,
|
||||||
|
315,
|
||||||
|
16,
|
||||||
|
85,
|
||||||
|
180,
|
||||||
|
89,
|
||||||
|
78,
|
||||||
|
62,
|
||||||
|
76,
|
||||||
|
61
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 5,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 50,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
78,
|
||||||
|
191,
|
||||||
|
80,
|
||||||
|
81,
|
||||||
|
82,
|
||||||
|
13,
|
||||||
|
312,
|
||||||
|
311,
|
||||||
|
310,
|
||||||
|
415,
|
||||||
|
308,
|
||||||
|
303,
|
||||||
|
302,
|
||||||
|
12,
|
||||||
|
72,
|
||||||
|
73,
|
||||||
|
183,
|
||||||
|
78,
|
||||||
|
264
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 135,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 40,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 70,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
107,
|
||||||
|
66,
|
||||||
|
105,
|
||||||
|
63,
|
||||||
|
70
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 135,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 40,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 70,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
336,
|
||||||
|
296,
|
||||||
|
334,
|
||||||
|
293,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 135,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 40,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 70,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
325
src/modules/facemask/drawings/fox.json
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
2,
|
||||||
|
167,
|
||||||
|
92,
|
||||||
|
216,
|
||||||
|
207,
|
||||||
|
187,
|
||||||
|
147,
|
||||||
|
137,
|
||||||
|
234,
|
||||||
|
127,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
103,
|
||||||
|
67,
|
||||||
|
109,
|
||||||
|
10,
|
||||||
|
151,
|
||||||
|
9,
|
||||||
|
8,
|
||||||
|
168,
|
||||||
|
6,
|
||||||
|
197,
|
||||||
|
195,
|
||||||
|
5,
|
||||||
|
3,
|
||||||
|
196,
|
||||||
|
188,
|
||||||
|
233,
|
||||||
|
243,
|
||||||
|
190,
|
||||||
|
56,
|
||||||
|
28,
|
||||||
|
27,
|
||||||
|
225,
|
||||||
|
130,
|
||||||
|
25,
|
||||||
|
110,
|
||||||
|
24,
|
||||||
|
23,
|
||||||
|
22,
|
||||||
|
233,
|
||||||
|
188,
|
||||||
|
196,
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
19,
|
||||||
|
94,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
2,
|
||||||
|
393,
|
||||||
|
322,
|
||||||
|
436,
|
||||||
|
427,
|
||||||
|
411,
|
||||||
|
376,
|
||||||
|
366,
|
||||||
|
454,
|
||||||
|
356,
|
||||||
|
389,
|
||||||
|
251,
|
||||||
|
284,
|
||||||
|
332,
|
||||||
|
297,
|
||||||
|
338,
|
||||||
|
10,
|
||||||
|
151,
|
||||||
|
9,
|
||||||
|
8,
|
||||||
|
168,
|
||||||
|
6,
|
||||||
|
197,
|
||||||
|
195,
|
||||||
|
5,
|
||||||
|
248,
|
||||||
|
419,
|
||||||
|
412,
|
||||||
|
453,
|
||||||
|
463,
|
||||||
|
414,
|
||||||
|
286,
|
||||||
|
258,
|
||||||
|
257,
|
||||||
|
445,
|
||||||
|
359,
|
||||||
|
255,
|
||||||
|
339,
|
||||||
|
254,
|
||||||
|
253,
|
||||||
|
252,
|
||||||
|
453,
|
||||||
|
412,
|
||||||
|
419,
|
||||||
|
248,
|
||||||
|
5,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
19,
|
||||||
|
94,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 130,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
188,
|
||||||
|
245,
|
||||||
|
189,
|
||||||
|
221,
|
||||||
|
222,
|
||||||
|
223,
|
||||||
|
46,
|
||||||
|
226,
|
||||||
|
31,
|
||||||
|
228,
|
||||||
|
229,
|
||||||
|
230,
|
||||||
|
231,
|
||||||
|
128,
|
||||||
|
233,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
110,
|
||||||
|
25,
|
||||||
|
130,
|
||||||
|
225,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
56,
|
||||||
|
190,
|
||||||
|
243,
|
||||||
|
233,
|
||||||
|
128,
|
||||||
|
188
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
412,
|
||||||
|
465,
|
||||||
|
413,
|
||||||
|
441,
|
||||||
|
442,
|
||||||
|
276,
|
||||||
|
446,
|
||||||
|
261,
|
||||||
|
448,
|
||||||
|
449,
|
||||||
|
450,
|
||||||
|
451,
|
||||||
|
357,
|
||||||
|
453,
|
||||||
|
252,
|
||||||
|
253,
|
||||||
|
254,
|
||||||
|
339,
|
||||||
|
255,
|
||||||
|
359,
|
||||||
|
445,
|
||||||
|
257,
|
||||||
|
258,
|
||||||
|
286,
|
||||||
|
414,
|
||||||
|
463,
|
||||||
|
453,
|
||||||
|
357,
|
||||||
|
412
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 70,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 0,
|
||||||
|
"stroke_B": 30,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
19,
|
||||||
|
1,
|
||||||
|
45,
|
||||||
|
1,
|
||||||
|
275
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
355,
|
||||||
|
371,
|
||||||
|
280,
|
||||||
|
352
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
277,
|
||||||
|
329,
|
||||||
|
347,
|
||||||
|
346,
|
||||||
|
340
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 225,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
47,
|
||||||
|
100,
|
||||||
|
118,
|
||||||
|
117,
|
||||||
|
111
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 225,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
126,
|
||||||
|
142,
|
||||||
|
50,
|
||||||
|
123
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
168,
|
||||||
|
107,
|
||||||
|
151,
|
||||||
|
336,
|
||||||
|
168,
|
||||||
|
447
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 0,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 0,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
167
src/modules/facemask/drawings/ghost.json
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 225,
|
||||||
|
"fill_S": 5,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 255,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
152,
|
||||||
|
148,
|
||||||
|
176,
|
||||||
|
149,
|
||||||
|
150,
|
||||||
|
136,
|
||||||
|
172,
|
||||||
|
58,
|
||||||
|
132,
|
||||||
|
93,
|
||||||
|
234,
|
||||||
|
127,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
103,
|
||||||
|
67,
|
||||||
|
109,
|
||||||
|
10,
|
||||||
|
338,
|
||||||
|
297,
|
||||||
|
332,
|
||||||
|
284,
|
||||||
|
251,
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
361,
|
||||||
|
288,
|
||||||
|
397,
|
||||||
|
365,
|
||||||
|
379,
|
||||||
|
378,
|
||||||
|
400,
|
||||||
|
377,
|
||||||
|
152
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 360,
|
||||||
|
"fill_S": 45,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 255,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
73,
|
||||||
|
180,
|
||||||
|
84,
|
||||||
|
17,
|
||||||
|
314,
|
||||||
|
404,
|
||||||
|
303,
|
||||||
|
312,
|
||||||
|
82,
|
||||||
|
73,
|
||||||
|
288
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 360,
|
||||||
|
"fill_S": 45,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 360,
|
||||||
|
"stroke_S": 60,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
13,
|
||||||
|
17
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 360,
|
||||||
|
"fill_S": 45,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 265,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
92,
|
||||||
|
73,
|
||||||
|
82,
|
||||||
|
13,
|
||||||
|
312,
|
||||||
|
303,
|
||||||
|
322
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 345,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 130,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
225,
|
||||||
|
100
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 345,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 130,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
221,
|
||||||
|
117
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 345,
|
||||||
|
"fill_S": 50,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 0,
|
||||||
|
"stroke_H": 130,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 0,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
453,
|
||||||
|
452,
|
||||||
|
451,
|
||||||
|
450,
|
||||||
|
449,
|
||||||
|
448,
|
||||||
|
261
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 360,
|
||||||
|
"fill_S": 45,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 265,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
160
src/modules/facemask/drawings/grey.json
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 25,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 80,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
152,
|
||||||
|
170,
|
||||||
|
138,
|
||||||
|
177,
|
||||||
|
93,
|
||||||
|
234,
|
||||||
|
127,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
103,
|
||||||
|
67,
|
||||||
|
109,
|
||||||
|
10,
|
||||||
|
338,
|
||||||
|
297,
|
||||||
|
332,
|
||||||
|
284,
|
||||||
|
251,
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
401,
|
||||||
|
367,
|
||||||
|
395,
|
||||||
|
152
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 80,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
142,
|
||||||
|
101,
|
||||||
|
118,
|
||||||
|
31,
|
||||||
|
226,
|
||||||
|
113,
|
||||||
|
225,
|
||||||
|
224,
|
||||||
|
223,
|
||||||
|
222,
|
||||||
|
221,
|
||||||
|
189,
|
||||||
|
245,
|
||||||
|
217,
|
||||||
|
209,
|
||||||
|
142
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 80,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
429,
|
||||||
|
437,
|
||||||
|
465,
|
||||||
|
413,
|
||||||
|
441,
|
||||||
|
442,
|
||||||
|
443,
|
||||||
|
444,
|
||||||
|
445,
|
||||||
|
342,
|
||||||
|
446,
|
||||||
|
261,
|
||||||
|
347,
|
||||||
|
330,
|
||||||
|
371,
|
||||||
|
429
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 80,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
89,
|
||||||
|
81,
|
||||||
|
38,
|
||||||
|
12,
|
||||||
|
268,
|
||||||
|
311,
|
||||||
|
319,
|
||||||
|
89
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 25,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
60,
|
||||||
|
60,
|
||||||
|
97
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 25,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
290,
|
||||||
|
328,
|
||||||
|
326
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 160,
|
||||||
|
"fill_S": 95,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 160,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 25,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
115
src/modules/facemask/drawings/operaMask.json
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 270,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 285,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
55,
|
||||||
|
65,
|
||||||
|
52,
|
||||||
|
53,
|
||||||
|
70,
|
||||||
|
71,
|
||||||
|
21,
|
||||||
|
139,
|
||||||
|
143,
|
||||||
|
111,
|
||||||
|
117,
|
||||||
|
118,
|
||||||
|
126,
|
||||||
|
198,
|
||||||
|
236,
|
||||||
|
3,
|
||||||
|
195,
|
||||||
|
196,
|
||||||
|
188,
|
||||||
|
233,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
110,
|
||||||
|
25,
|
||||||
|
130,
|
||||||
|
113,
|
||||||
|
29,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
56,
|
||||||
|
190,
|
||||||
|
243,
|
||||||
|
233,
|
||||||
|
188,
|
||||||
|
196,
|
||||||
|
195
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 270,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 285,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
8,
|
||||||
|
285,
|
||||||
|
295,
|
||||||
|
282,
|
||||||
|
283,
|
||||||
|
300,
|
||||||
|
301,
|
||||||
|
251,
|
||||||
|
368,
|
||||||
|
372,
|
||||||
|
340,
|
||||||
|
346,
|
||||||
|
347,
|
||||||
|
355,
|
||||||
|
420,
|
||||||
|
456,
|
||||||
|
248,
|
||||||
|
195,
|
||||||
|
419,
|
||||||
|
412,
|
||||||
|
453,
|
||||||
|
253,
|
||||||
|
254,
|
||||||
|
339,
|
||||||
|
255,
|
||||||
|
359,
|
||||||
|
342,
|
||||||
|
259,
|
||||||
|
257,
|
||||||
|
258,
|
||||||
|
286,
|
||||||
|
414,
|
||||||
|
463,
|
||||||
|
453,
|
||||||
|
412,
|
||||||
|
419,
|
||||||
|
195
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 270,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 285,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
188
src/modules/facemask/drawings/operaMask2.json
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
150,
|
||||||
|
150,
|
||||||
|
212,
|
||||||
|
216,
|
||||||
|
206,
|
||||||
|
98,
|
||||||
|
97,
|
||||||
|
2,
|
||||||
|
462,
|
||||||
|
250,
|
||||||
|
459,
|
||||||
|
440,
|
||||||
|
363,
|
||||||
|
456,
|
||||||
|
399,
|
||||||
|
412,
|
||||||
|
465,
|
||||||
|
413,
|
||||||
|
441,
|
||||||
|
442,
|
||||||
|
443,
|
||||||
|
444,
|
||||||
|
445,
|
||||||
|
342,
|
||||||
|
265,
|
||||||
|
372,
|
||||||
|
264,
|
||||||
|
389,
|
||||||
|
251,
|
||||||
|
284,
|
||||||
|
332,
|
||||||
|
297,
|
||||||
|
338,
|
||||||
|
10,
|
||||||
|
109,
|
||||||
|
67,
|
||||||
|
103,
|
||||||
|
54,
|
||||||
|
21,
|
||||||
|
162,
|
||||||
|
127,
|
||||||
|
34,
|
||||||
|
143,
|
||||||
|
35,
|
||||||
|
226,
|
||||||
|
113,
|
||||||
|
225,
|
||||||
|
224,
|
||||||
|
223,
|
||||||
|
222,
|
||||||
|
221,
|
||||||
|
189,
|
||||||
|
244,
|
||||||
|
233,
|
||||||
|
232,
|
||||||
|
231,
|
||||||
|
230,
|
||||||
|
229,
|
||||||
|
228,
|
||||||
|
31,
|
||||||
|
226,
|
||||||
|
35,
|
||||||
|
143,
|
||||||
|
34,
|
||||||
|
127,
|
||||||
|
234,
|
||||||
|
93,
|
||||||
|
132,
|
||||||
|
58,
|
||||||
|
172,
|
||||||
|
136,
|
||||||
|
150
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
361,
|
||||||
|
288,
|
||||||
|
397,
|
||||||
|
365,
|
||||||
|
379,
|
||||||
|
378,
|
||||||
|
400,
|
||||||
|
377,
|
||||||
|
152,
|
||||||
|
148,
|
||||||
|
176,
|
||||||
|
149,
|
||||||
|
150,
|
||||||
|
212,
|
||||||
|
216,
|
||||||
|
206,
|
||||||
|
98,
|
||||||
|
97,
|
||||||
|
2,
|
||||||
|
164,
|
||||||
|
0,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
82,
|
||||||
|
81,
|
||||||
|
80,
|
||||||
|
191,
|
||||||
|
78,
|
||||||
|
95,
|
||||||
|
88,
|
||||||
|
178,
|
||||||
|
87,
|
||||||
|
14,
|
||||||
|
317,
|
||||||
|
402,
|
||||||
|
318,
|
||||||
|
324,
|
||||||
|
308,
|
||||||
|
415,
|
||||||
|
310,
|
||||||
|
311,
|
||||||
|
312,
|
||||||
|
13,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
0,
|
||||||
|
164,
|
||||||
|
2,
|
||||||
|
250,
|
||||||
|
459,
|
||||||
|
440,
|
||||||
|
363,
|
||||||
|
456,
|
||||||
|
399,
|
||||||
|
412,
|
||||||
|
465,
|
||||||
|
413,
|
||||||
|
464,
|
||||||
|
453,
|
||||||
|
452,
|
||||||
|
451,
|
||||||
|
450,
|
||||||
|
449,
|
||||||
|
448,
|
||||||
|
261,
|
||||||
|
446,
|
||||||
|
342,
|
||||||
|
265,
|
||||||
|
372,
|
||||||
|
264,
|
||||||
|
389,
|
||||||
|
397
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
206
src/modules/facemask/drawings/operaMask3.json
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
4,
|
||||||
|
45,
|
||||||
|
218,
|
||||||
|
235,
|
||||||
|
203,
|
||||||
|
205,
|
||||||
|
123,
|
||||||
|
227,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
104,
|
||||||
|
69,
|
||||||
|
108,
|
||||||
|
151,
|
||||||
|
9,
|
||||||
|
8,
|
||||||
|
168,
|
||||||
|
6,
|
||||||
|
122,
|
||||||
|
245,
|
||||||
|
244,
|
||||||
|
243,
|
||||||
|
190,
|
||||||
|
56,
|
||||||
|
28,
|
||||||
|
27,
|
||||||
|
225,
|
||||||
|
130,
|
||||||
|
25,
|
||||||
|
110,
|
||||||
|
24,
|
||||||
|
23,
|
||||||
|
22,
|
||||||
|
26,
|
||||||
|
112,
|
||||||
|
243,
|
||||||
|
244,
|
||||||
|
245,
|
||||||
|
122,
|
||||||
|
6,
|
||||||
|
197,
|
||||||
|
195,
|
||||||
|
5,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
130,
|
||||||
|
46,
|
||||||
|
223,
|
||||||
|
222,
|
||||||
|
221,
|
||||||
|
189,
|
||||||
|
245,
|
||||||
|
188,
|
||||||
|
174,
|
||||||
|
114,
|
||||||
|
121,
|
||||||
|
231,
|
||||||
|
24,
|
||||||
|
23,
|
||||||
|
22,
|
||||||
|
26,
|
||||||
|
112,
|
||||||
|
243,
|
||||||
|
190,
|
||||||
|
56,
|
||||||
|
28,
|
||||||
|
27,
|
||||||
|
225,
|
||||||
|
247,
|
||||||
|
25,
|
||||||
|
130,
|
||||||
|
397
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
4,
|
||||||
|
275,
|
||||||
|
438,
|
||||||
|
455,
|
||||||
|
423,
|
||||||
|
425,
|
||||||
|
352,
|
||||||
|
447,
|
||||||
|
389,
|
||||||
|
251,
|
||||||
|
284,
|
||||||
|
333,
|
||||||
|
299,
|
||||||
|
337,
|
||||||
|
151,
|
||||||
|
9,
|
||||||
|
8,
|
||||||
|
168,
|
||||||
|
6,
|
||||||
|
351,
|
||||||
|
465,
|
||||||
|
464,
|
||||||
|
463,
|
||||||
|
414,
|
||||||
|
286,
|
||||||
|
258,
|
||||||
|
257,
|
||||||
|
445,
|
||||||
|
359,
|
||||||
|
255,
|
||||||
|
339,
|
||||||
|
254,
|
||||||
|
253,
|
||||||
|
252,
|
||||||
|
256,
|
||||||
|
341,
|
||||||
|
463,
|
||||||
|
464,
|
||||||
|
465,
|
||||||
|
351,
|
||||||
|
6,
|
||||||
|
197,
|
||||||
|
195,
|
||||||
|
5,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": [
|
||||||
|
399,
|
||||||
|
412,
|
||||||
|
465,
|
||||||
|
413,
|
||||||
|
441,
|
||||||
|
442,
|
||||||
|
443,
|
||||||
|
276,
|
||||||
|
342,
|
||||||
|
255,
|
||||||
|
467,
|
||||||
|
445,
|
||||||
|
257,
|
||||||
|
258,
|
||||||
|
286,
|
||||||
|
414,
|
||||||
|
463,
|
||||||
|
341,
|
||||||
|
256,
|
||||||
|
252,
|
||||||
|
253,
|
||||||
|
254,
|
||||||
|
451,
|
||||||
|
350,
|
||||||
|
343,
|
||||||
|
399
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 155,
|
||||||
|
"fill_S": 0,
|
||||||
|
"fill_B": 100,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 330,
|
||||||
|
"stroke_S": 50,
|
||||||
|
"stroke_B": 100,
|
||||||
|
"stroke_O": 0,
|
||||||
|
"indices": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
159
src/modules/facemask/drawings/pumpkin.json
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
{
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 90,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
10,
|
||||||
|
338,
|
||||||
|
297,
|
||||||
|
332,
|
||||||
|
284,
|
||||||
|
251,
|
||||||
|
389,
|
||||||
|
356,
|
||||||
|
454,
|
||||||
|
323,
|
||||||
|
361,
|
||||||
|
288,
|
||||||
|
397,
|
||||||
|
365,
|
||||||
|
379,
|
||||||
|
378,
|
||||||
|
400,
|
||||||
|
377,
|
||||||
|
152,
|
||||||
|
148,
|
||||||
|
176,
|
||||||
|
149,
|
||||||
|
150,
|
||||||
|
136,
|
||||||
|
172,
|
||||||
|
58,
|
||||||
|
132,
|
||||||
|
93,
|
||||||
|
234,
|
||||||
|
127,
|
||||||
|
162,
|
||||||
|
21,
|
||||||
|
54,
|
||||||
|
103,
|
||||||
|
67,
|
||||||
|
109,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
6,
|
||||||
|
6,
|
||||||
|
294,
|
||||||
|
64,
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
31,
|
||||||
|
65,
|
||||||
|
233,
|
||||||
|
31
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
465,
|
||||||
|
258,
|
||||||
|
265,
|
||||||
|
253,
|
||||||
|
465
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
0,
|
||||||
|
312,
|
||||||
|
302,
|
||||||
|
311,
|
||||||
|
303,
|
||||||
|
310,
|
||||||
|
270,
|
||||||
|
409,
|
||||||
|
291,
|
||||||
|
375,
|
||||||
|
320,
|
||||||
|
324,
|
||||||
|
404,
|
||||||
|
402,
|
||||||
|
315,
|
||||||
|
14,
|
||||||
|
85,
|
||||||
|
178,
|
||||||
|
180,
|
||||||
|
88,
|
||||||
|
91,
|
||||||
|
95,
|
||||||
|
146,
|
||||||
|
61,
|
||||||
|
185,
|
||||||
|
40,
|
||||||
|
42,
|
||||||
|
39,
|
||||||
|
81,
|
||||||
|
37,
|
||||||
|
82
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fill_H": 40,
|
||||||
|
"fill_S": 100,
|
||||||
|
"fill_B": 0,
|
||||||
|
"fill_O": 100,
|
||||||
|
"stroke_H": 30,
|
||||||
|
"stroke_S": 100,
|
||||||
|
"stroke_B": 50,
|
||||||
|
"stroke_O": 100,
|
||||||
|
"indices": [
|
||||||
|
397
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
src/modules/facemask/face_landmarker_keypoints.png
Normal file
|
After Width: | Height: | Size: 933 KiB |
BIN
src/modules/facemask/face_landmarker_model/face_landmarker.task
Normal file
BIN
src/modules/facemask/glowing.mp4
Normal file
BIN
src/modules/facemask/hand_landmarker_model/hand_landmarker.task
Normal file
1797
src/modules/facemask/images/icons.ai
Normal file
BIN
src/modules/facemask/images/savejson.png
Normal file
|
After Width: | Height: | Size: 258 B |
BIN
src/modules/facemask/images/screenshot.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
src/modules/facemask/images/shapeindex_down.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
src/modules/facemask/images/shapeindex_up.png
Normal file
|
After Width: | Height: | Size: 306 B |