From ccda0a7736a7686b4b1fae98395faa3e86aa2967 Mon Sep 17 00:00:00 2001 From: Khyretos Date: Fri, 5 Jan 2024 04:14:49 +0100 Subject: [PATCH] betterttv, send translated message. --- .gitignore | 1 + backend/loquendoBot_backend.py | 11 +++-- src/css/chat.css | 15 +++++++ src/index.html | 29 ++++++++++-- src/js/backend.js | 82 ++++++++++++++++++++++++++++------ src/js/chat.js | 28 ++++++++++-- src/js/renderer.js | 41 ++++++++++++----- src/js/settings.js | 23 ++++++++++ src/js/twitch.js | 70 +++++++++++++++++++++++++++-- src/main.js | 6 ++- 10 files changed, 265 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 87c3a5b..6c26e1d 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ backend/* backend/loquendoBot_backend.exe src/config/twitch-emotes.json dist/* +src/config/betterttv-emotes.json diff --git a/backend/loquendoBot_backend.py b/backend/loquendoBot_backend.py index 58d624b..ba3c48b 100644 --- a/backend/loquendoBot_backend.py +++ b/backend/loquendoBot_backend.py @@ -236,11 +236,14 @@ def get_translation(): request_data = request.json message = request_data.get("message", "") detectedLanguage = request_data.get("language", "") - translated = MyMemoryTranslator( - source=detectedLanguage, target=settings["LANGUAGE"]["TRANSLATE_TO"] - ).translate(message) + 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": "Could not translate, continuing with next language"}), 500 + return jsonify({"error": str(e), "code":500 }), 500 return jsonify({"translation": translated}), 200 diff --git a/src/css/chat.css b/src/css/chat.css index da133b0..9685b95 100644 --- a/src/css/chat.css +++ b/src/css/chat.css @@ -541,6 +541,10 @@ h1 { margin: 20px 0px 0px 0px; } +.translation-message.user { + margin: -20px 0px 0px 0px; +} + .translation-icon { position: relative; padding: 0px 0px 0px 0px; @@ -558,3 +562,14 @@ h1 { height: 20px !important; left: 18px; } + +.flag-icon.user { + left: -18px; + top: -15px; +} + +.user-flag { + left: unset; + right: 18px; + top: -65px; +} diff --git a/src/index.html b/src/index.html index c9c820e..ef27d1c 100644 --- a/src/index.html +++ b/src/index.html @@ -294,6 +294,16 @@ tip="All translated messages will be send to primary TTS voice but if message is detected in Secondary TTS language it will output it to the Secondary TTS voice" > +
+
Send translated messages
+ + + +
@@ -371,6 +381,17 @@ tip="Test Twitch credentials" > +
+
Get BetterTTV emotes
+ + +
@@ -583,19 +604,19 @@ -
+
In
-
+
-
+
Out
-
+
diff --git a/src/js/backend.js b/src/js/backend.js index b2d318a..b6c2600 100644 --- a/src/js/backend.js +++ b/src/js/backend.js @@ -44,10 +44,46 @@ async function getInstalledVoices() { 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 (!languageBox) { + twitch.sendMessage( + `[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${message.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]; - const languageBox = document.getElementById(message.messageId).getElementsByClassName('language-icon flag-icon')[0]; languageBox.classList = `fi fi-${message.language.detectedLanguage.ISO3166} fis language-icon flag-icon`; languageBox.setAttribute('tip', message.language.detectedLanguage.name); @@ -60,7 +96,6 @@ function setTranslatedMessage(message) { const translationIcon = document.createElement('div'); translationIcon.className = 'translation-icon'; const languageElement = document.createElement('span'); - const language = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO); languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis flag-icon`; languageElement.setAttribute('tip', message.language.selectedLanguage.name); addSingleTooltip(languageElement); @@ -95,7 +130,6 @@ async function getTranslatedMessage(message) { }, body: JSON.stringify({ message: message.message, - remainder: message.remainingDetectedLanguages, language: message.language.detectedLanguage.IETF }) // Convert the data to JSON and include it in the request body }; @@ -111,7 +145,6 @@ async function getTranslatedMessage(message) { `[${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, @@ -124,21 +157,42 @@ async function getTranslatedMessage(message) { return message.language.detectedLanguage; } else { console.error(responseData); - if (message.remainingDetectedLanguages.length > 0) { - message.language.detectedLanguage = getLanguageProperties(message.remainingDetectedLanguages[0]); - message.remainingDetectedLanguages.shift(); - return getTranslatedMessage(message); - } else { - message.message = 'Error, Could not translate message'; + if (responseData.code === 500) { + if (message.remainingDetectedLanguages.length > 0) { + message.language.detectedLanguage = getLanguageProperties(message.remainingDetectedLanguages[0]); + message.remainingDetectedLanguages.shift(); + return getTranslatedMessage(message); + } else { + message.message = 'Error, Could not translate message'; + message.language.detectedLanguage = getLanguageProperties('en-GB'); + return getTranslatedMessage(message); + } + } + if (responseData.code === 429) { message.language.detectedLanguage = getLanguageProperties('en-GB'); - return getTranslatedMessage(message); + setTranslatedMessage({ + originalMessage: message.message, + translation: 'Rate limit exceeded, please change translation service.', + messageId: message.messageId, + language: message.language, + formattedMessage: message.formattedMessage, + username: message.username, + logoUrl: message.logoUrl + }); } } } catch (error) { console.error('Error sending termination signal:', error); - message.message = 'Error, Could not translate message'; message.language.detectedLanguage = getLanguageProperties('en-GB'); - getTranslatedMessage(message); + 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 + }); } } @@ -363,4 +417,4 @@ ipcRenderer.on('quit-event', async () => { } }); -module.exports = { getInternalTTSAudio, getDetectedLanguage }; +module.exports = { getInternalTTSAudio, getDetectedLanguage, getTranslatedMessage }; diff --git a/src/js/chat.js b/src/js/chat.js index ebc8eda..827a014 100644 --- a/src/js/chat.js +++ b/src/js/chat.js @@ -1,4 +1,4 @@ -/* global messageTemplates, emojiPicker, settings, getPostTime, showChatMessage, twitch */ +/* global messageTemplates,getLanguageProperties, backend, messageId emojiPicker, settings, getPostTime, showChatMessage, twitch */ async function getResponse() { const userText = document.querySelector('#textInput').value; @@ -8,8 +8,11 @@ async function getResponse() { return; } + messageId++; + // Create chat message from received data const article = document.createElement('article'); + article.setAttribute('id', messageId); article.className = 'msg-container user'; article.innerHTML = messageTemplates.userTemplate; @@ -29,9 +32,7 @@ async function getResponse() { const msg = article.querySelector('.msg-box'); if (msg) { - console.log(0); await replaceChatMessageWithCustomEmojis(userText).then(data => { - // console.log(data); msg.innerHTML = data; // Appends the message to the main chat box (shows the message) @@ -39,6 +40,23 @@ async function getResponse() { 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 = ''; }); @@ -167,3 +185,7 @@ displayPanelX('.item', '#btnChatCreator', '#btnChatCreator'); // #region Show/Hide Theme Creator // #endregion + +module.exports = { + replaceChatMessageWithCustomEmojis +}; diff --git a/src/js/renderer.js b/src/js/renderer.js index 6fbe662..6ccf41a 100644 --- a/src/js/renderer.js +++ b/src/js/renderer.js @@ -23,8 +23,12 @@ const settings = main.settings; 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) const amazonVoices = fs.readFileSync(path.join(__dirname, './config/amazonVoices.txt')).toString().split('\r\n'); -const emoteListSavePath = +const twitchEmoteListSavePath = main.isPackaged === true ? path.join(resourcesPath, './twitch-emotes.json') : path.join(resourcesPath, './config/twitch-emotes.json'); +const betterTtvEmoteListSavePath = + main.isPackaged === true + ? path.join(resourcesPath, './betterttv-emotes.json') + : path.join(resourcesPath, './config/betterttv-emotes.json'); // html elements const root = document.documentElement; @@ -178,8 +182,16 @@ function setLanguagesinSelectx(languageSelector, language) { const languageSelectContent = languageSelect.querySelector('.pop-content'); languageSelectContent.addEventListener('click', e => { - console.log(e.target); + 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, ini.stringify(settings)); setSelectedLanguageinSelect(languageSelect, language); }); @@ -378,15 +390,22 @@ function setZoomLevel(currentZoom, zoomIn) { document.body.querySelector('#ZOOMLEVEL').value = (settings.GENERAL.ZOOMLEVEL * 100).toFixed(0); } -if (fs.existsSync(emoteListSavePath)) { - fs.readFile(emoteListSavePath, 'utf8', (error, data) => { - if (error) { - console.log(error); - return; - } - const emotes = JSON.parse(data); - emojiPicker.customEmoji = emotes; - }); +// TODO: refactor +let twitchEmotes = null; +if (fs.existsSync(twitchEmoteListSavePath)) { + const xxx = fs.readFileSync(twitchEmoteListSavePath); + twitchEmotes = JSON.parse(xxx); + emojiPicker.customEmoji = [...twitchEmotes]; +} +let betterTtvEmotes = null; +if (fs.existsSync(betterTtvEmoteListSavePath)) { + const xxx = fs.readFileSync(betterTtvEmoteListSavePath); + betterTtvEmotes = JSON.parse(xxx); + emojiPicker.customEmoji = [...betterTtvEmotes]; +} + +if (twitchEmotes && betterTtvEmotes) { + emojiPicker.customEmoji = [...twitchEmotes, ...betterTtvEmotes]; } function getLanguageProperties(languageToDetect) { diff --git a/src/js/settings.js b/src/js/settings.js index a6c99af..ea58ce5 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -16,6 +16,7 @@ function getGeneralSettings() { // Language detection document.body.querySelector('#USE_DETECTION').checked = settings.LANGUAGE.USE_DETECTION; document.body.querySelector('#OUTPUT_TO_TTS').checked = settings.LANGUAGE.OUTPUT_TO_TTS; + document.body.querySelector('#SEND_TRANSLATION').checked = settings.LANGUAGE.SEND_TRANSLATION; document.body.querySelector('#BROADCAST_TRANSLATION').checked = settings.LANGUAGE.BROADCAST_TRANSLATION; // TTS @@ -323,6 +324,11 @@ document.body.querySelector('#Info_USERNAME').addEventListener('click', async () createNotification('Saved OAuth token!', 'success'); }); +document.body.querySelector('#GetBetterTtvEmotes').addEventListener('click', async () => { + twitch.getBetterTtvGLobalEmotes(); + createNotification('Saved BetterTTV emotes!', 'success'); +}); + const hideInputToggleButton = document.body.querySelectorAll('.password-toggle-btn .password-toggle-icon .fa-eye-slash'); hideInputToggleButton.forEach(item => { item.addEventListener('click', () => { @@ -518,6 +524,23 @@ document.body.querySelector('#USE_STT').addEventListener('change', () => { createNotification(`${toggle ? 'Enabled' : 'Disabled'} speech to text!`, 'success'); }); +function toggleSendTranslation() { + const toggle = settings.LANGUAGE.SEND_TRANSLATION; + const inputs = document.getElementsByClassName('send-translation'); + toggleRadio(toggle, inputs); +} + +toggleSendTranslation(); + +document.body.querySelector('#SEND_TRANSLATION').addEventListener('change', () => { + const toggle = document.getElementById('SEND_TRANSLATION').checked; + settings.LANGUAGE.SEND_TRANSLATION = toggle; + fs.writeFileSync(settingsPath, ini.stringify(settings)); + const inputs = document.getElementsByClassName('send-translation'); + toggleRadio(toggle, inputs); + createNotification(`${toggle ? 'Enabled' : 'Disabled'} Sending translations!`, 'success'); +}); + document.body.querySelector('#OUTPUT_TO_TTS').addEventListener('change', () => { let toggle = document.getElementById('OUTPUT_TO_TTS').checked; if (!settings.TTS.USE_TTS) { diff --git a/src/js/twitch.js b/src/js/twitch.js index bc9d74a..dcf8e95 100644 --- a/src/js/twitch.js +++ b/src/js/twitch.js @@ -1,4 +1,4 @@ -/* global client, playNotificationSound, messageId, addSingleTooltip, settingsPath, fs, ini, backend, main, path, resourcesPath, customEmojis, emojiPicker,config, settings, options, sound, showChatMessage, messageTemplates, getPostTime */ +/* global client, playNotificationSound, chat, replaceChatMessageWithCustomEmojis, messageId, addSingleTooltip, settingsPath, fs, ini, backend, main, path, resourcesPath, customEmojis, emojiPicker,config, settings, options, sound, showChatMessage, messageTemplates, getPostTime */ const tmi = require('tmi.js'); const axios = require('axios'); @@ -134,11 +134,13 @@ async function displayTwitchMessage(logoUrl, username, messageObject, filteredMe }); } - showChatMessage(article); + await chat.replaceChatMessageWithCustomEmojis(formattedMessage.innerHTML).then(data => { + formattedMessage.innerHTML = data; + showChatMessage(article); + }); if (settings.LANGUAGE.USE_DETECTION) { await backend.getDetectedLanguage({ message: filteredMessage, messageId, username, logoUrl, formattedMessage }).then(language => { - console.log(language); const languageElement = document.createElement('span'); languageElement.classList = `fi fi-${language.ISO3166} fis language-icon flag-icon`; languageElement.setAttribute('tip', language.name); @@ -250,6 +252,57 @@ function formatTwitchEmotes(channel) { saveTwitchEmotesToFile(customEmojis); } +function saveBetterTtvEmotesToFile(BetterTtvEmotes) { + const data = JSON.stringify(BetterTtvEmotes); + const savePath = + main.isPackaged === true + ? path.join(resourcesPath, './betterttv-emotes.json') + : path.join(resourcesPath, './config/betterttv-emotes.json'); + fs.writeFile(savePath, data, error => { + if (error) { + console.error(error); + + throw error; + } + }); +} + +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); + }); + emojiPicker.customEmoji = customEmojis; + saveBetterTtvEmotesToFile(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 getTwitchUserFollows(paginationToken) { let url = ''; if (!paginationToken) { @@ -449,4 +502,13 @@ function getTwitchUserId() { // Reconnect 10s later // setTimeout(ws.reconnect, 10e3); -module.exports = { sendMessage, ping, client, getUserAvailableTwitchEmotes, getTwitchChannelId, getTwitchUserId, checkIfTokenIsValid }; +module.exports = { + sendMessage, + ping, + client, + getBetterTtvGLobalEmotes, + getUserAvailableTwitchEmotes, + getTwitchChannelId, + getTwitchUserId, + checkIfTokenIsValid +}; diff --git a/src/main.js b/src/main.js index fc584ac..6296629 100644 --- a/src/main.js +++ b/src/main.js @@ -143,7 +143,11 @@ async function createIniFile() { TRANSLATE_TO: 'none', LANGUAGE_INDEX: '0', BROADCAST_TRANSLATION: false, - OUTPUT_TO: false + OUTPUT_TO_TTS: false, + TRANSLATE_TO_INDEX: 0, + SEND_TRANSLATION: true, + SEND_TRANSLATION_IN: 'none', + SEND_TRANSLATION_OUT: 'none' }, TTS: { USE_TTS: false,