betterttv, send translated message.

This commit is contained in:
Khyretos 2024-01-05 04:14:49 +01:00
parent 0efb495339
commit ccda0a7736
10 changed files with 265 additions and 41 deletions

1
.gitignore vendored
View file

@ -108,3 +108,4 @@ backend/*
backend/loquendoBot_backend.exe
src/config/twitch-emotes.json
dist/*
src/config/betterttv-emotes.json

View file

@ -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

View file

@ -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;
}

View file

@ -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"
></i>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Send translated messages</div>
<input type="checkbox" id="SEND_TRANSLATION" class="checkbox" />
<label for="SEND_TRANSLATION" class="toggle-small" style="margin-right: 260px"></label>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_SEND_TRANSLATION"
tip="Enable sending translated messages to the chat."
></i>
</div>
</fieldset>
<fieldset id="NotificationMenu" class="AdvancedMenu">
@ -371,6 +381,17 @@
tip="Test Twitch credentials"
></i>
</div>
<div class="AdvancedMenuRow inputTwitch">
<div class="AdvancedMenuLabel">Get BetterTTV emotes</div>
<button type="text" class="AdvancedMenuButton" id="GetBetterTtvEmotes">
<img src="https://cdn.betterttv.net/emote/5f1b0186cf6d2144653d2970/3x.webp" width="30px" height="30px" />
</button>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_BETTERTTV_EMOTES"
tip="Test Twitch credentials"
></i>
</div>
</fieldset>
<fieldset id="AdvancedMenuServer" class="AdvancedMenu">
@ -583,19 +604,19 @@
</button>
<emoji-picker class="dark"></emoji-picker>
</div>
<div class="pop in">
<div class="pop in send-translation">
<div class="miniText">In</div>
<button class="SmallButton">
<i class="fa-solid fa-globe fa-2x" aria-hidden="true"></i>
</button>
<div class="pop-content"></div>
<div class="pop-content" id="SEND_TRANSLATION_IN"></div>
</div>
<div class="pop out">
<div class="pop out send-translation">
<div class="miniText">Out</div>
<button class="SmallButton">
<i class="fa-solid fa-globe fa-2x" aria-hidden="true"></i>
</button>
<div class="pop-content"></div>
<div class="pop-content" id="SEND_TRANSLATION_OUT"></div>
</div>
<!-- User text input-->
<input id="textInput" class="input-box" type="text" name="msg" placeholder="Tap 'Enter' to send a message" />

View file

@ -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 };

View file

@ -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
};

View file

@ -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) {

View file

@ -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) {

View file

@ -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
};

View file

@ -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,