You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

812 lines
35 KiB

<html>
<head>
<!-- Materialze style -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="engines.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<style>
#drop_zone {
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 25px;
text-align: center;
font-size: 20pt;
font-weight: bold;
font-family: 'Arial';
color: #bbb;
width: 90%;
height: 60px;
}
.error {
border: 2px solid red;
}
</style>
<script type="text/javascript">
var webServers = null;
var gOnChange;
function showHideSettings() {
$('.variable').hide();
var type = $('#type').val();
for (var j = 0; j < sayitOptions[type].params.length; j++) {
$('#tr_' + sayitOptions[type].params[j]).show();
}
if (type === 'googleHome') {
$('.announceValue').hide();
$('.announce').hide();
$('.system').hide();
$('.googleHome').show();
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').removeClass('disabled');
fillGoogleHome();
}
});
} else {
$('.googleHome').hide();
$('.announceValue').show();
$('.announce').show();
}
$('.engine').hide();
var engine = $('#engine').val();
if (!sayitEngines[engine] || sayitOptions[type].params.indexOf('engine') === -1) return;
for (var i = 0; i < sayitEngines[engine].params.length; i++) {
$('#tr_' + sayitEngines[engine].params[i]).show();
if (sayitEngines[engine][sayitEngines[engine].params[i]]) {
var arr = sayitEngines[engine][sayitEngines[engine].params[i]];
var param = sayitEngines[engine].params[i];
var $val = $('#' + param);
if (arr.length) {
var val = $val.val();
var text = '';
for (var p = 0; p < arr.length; p++) {
text += '<option value="' + arr[p] + '">' + arr[p] + '</option>';
}
$val.html(text);
if (val) {
$val.val(val);
} else {
$val.val(arr[0]);
}
} else {
$val.html('');
}
}
}
if (type !== 'system') {
$('.system').hide();
} else {
$('.system').show();
}
if (!$('#announce').val()) {
$('.announce').hide();
$('#play').addClass('disabled');
} else {
$('.announce').show();
$('#play').removeClass('disabled');
}
}
function fillSonosDevices(elem, current) {
socket.emit('getObjectView', 'system', 'channel', {startkey: 'sonos.', endkey: 'sonos.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
$(elem).select();
}
$(elem).val(current);
});
}
function fillMpdDevice(elem, current) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.mpd.', endkey: 'system.adapter.mpd.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
$(elem).append('<option value="' + n + '">' + n + '</option>');
}
$(elem).select();
}
$(elem).val(current);
});
}
function fillChromecastDevices(elem, current) {
socket.emit('getObjectView', 'system', 'device', {startkey: 'chromecast.', endkey: 'chromecast.\u9999'}, function (err, res) {
if (!err && res) {
for (var i = 0; i < res.rows.length; i++) {
$(elem).append('<option value="' + res.rows[i].id + '">' + res.rows[i].id + '</option>');
}
}
$(elem).select();
$(elem).val(current);
});
}
function fillGoogleHome() {
$('#search').addClass('disabled');
sendTo(null, 'browseChromecast', null, function (list) {
var text = '<option value="">' + _('select') + '</option>';
if (list) {
for (var i = 0; i < list.length; i++) {
text += '<option value="' + list[i].ip + '">' + list[i].name + '[' + list[i].ip + ']</option>';
}
$('#googleHome').html(text).prop('disabled', false).off('change').on('change', function () {
$('#server').val($(this).val()).trigger('change');
});
} else {
$('#googleHome').html('<option value="">' + _('error') + '</option>').prop('disabled', true);
}
$('#search').removeClass('disabled');
$('#googleHome').select();
});
}
function ip2hex(ip) {
var octets = ip.split('.');
if (octets.length !== 4) {
return 0;
}
var result = 0;
for (var i = 0; i < octets.length; ++i) {
var octet = parseInt(octets[i], 10);
if (Number.isNaN(octet) || octet < 0 || octet > 255) {
throw new Error("Each octet must be between 0 and 255");
}
result |= octet << ((octets.length - i) * 8);
}
return result;
}
function checkWeb(elem, current) {
var web = $('#web').val();
for (var i = 0; i < webServers.length; i++) {
if (webServers[i].id === 'system.adapter.' + web) {
if (webServers[i].value.native.auth) {
showMessage(_('Cannot use web server with authentication'), null, 'info');
}
if (webServers[i].value.native.bind === 'localhost' || webServers[i].value.native.bind === '127.0.0.1' || webServers[i].value.native.bind === '::1') {
showMessage(_('Cannot use web server only on localhost'), null, 'info');
}
if (webServers[i].value.native.bind === '0.0.0.0') {
$('#tr_webServer').show();
var $webServer = $('#webServer');
$webServer.html('');
// read all ipv4 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
var text = '';
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv4' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
text += '<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '" data-mask="' + obj.native.hardware.networkInterfaces[iface][i].netmask + '">[IPv4] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>';
}
}
}
}
}
$webServer.html(text);
if (current) {
$webServer.val(current);
}
$webServer.select();
$webServer.off('change').on('change', function () {
gOnChange();
var ip = $(this).val();
var netmask = $(this).find('option[value="' + ip + '"]').data('mask');
var server;
var type = $('#type').val();
if (type === 'googleHome') {
server = $('#server').val();
} else if (type === 'sonos') {
server = $('#device').val().split('.')[3].replace('_', '.').replace('_', '.').replace('_', '.');
} else if (type === 'chromecast') {
server = $('#cDevice').val();
} else if (type === 'mpd') {
server = $('#mpd_device').val();
}
if (server && ip && netmask && server.indexOf(':') === -1 && ip.indexOf(':') === -1) {
ip = ip2hex(ip);
netmask = ip2hex(netmask);
server = ip2hex(server);
if ((ip & netmask) !== (server & netmask)) {
$webServer.addClass('error').attr('title', _('IP not accessible for server'));
} else {
$webServer.removeClass('error').attr('title', '');
}
} else {
$webServer.removeClass('error');
}
});
});
} else if (webServers[i].value.native.bind === '::') {
// read all ipv6 addresses of host
socket.emit('getObject', 'system.host.' + webServers[i].value.common.host, function (err, obj) {
if (!err && obj && obj.native) {
for (var iface in obj.native.hardware.networkInterfaces) {
if (obj.native.hardware.networkInterfaces.hasOwnProperty(iface)) {
for (var i = 0; i < obj.native.hardware.networkInterfaces[iface].length; i++) {
if (obj.native.hardware.networkInterfaces[iface][i].family === 'IPv6' && !obj.native.hardware.networkInterfaces[iface][i].internal) {
$('#webServer').append('<option value="' + obj.native.hardware.networkInterfaces[iface][i].address + '">[IPv6] ' + obj.native.hardware.networkInterfaces[iface][i].address + ' - ' + iface + '</option>');
}
}
}
}
}
if (current) {
$('#webServer').val(current);
}
});
$('#webServer').select();
} else {
$('#tr_webServer').hide();
}
}
}
}
function fillWebServices(elem, current, type, webServer) {
socket.emit('getObjectView', 'system', 'instance', {startkey: 'system.adapter.web.', endkey: 'system.adapter.web.\u9999'}, function (err, res) {
if (!err && res) {
webServers = res.rows;
for (var i = 0; i < res.rows.length; i++) {
var n = res.rows[i].id.replace('system.adapter.', '');
var auth = res.rows[i].value.native.auth ? 'data-auth="true"' : '';
$(elem).append('<option value="' + n + '" ' + auth + '>' + n + '</option>');
}
}
$(elem).val(current);
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'googleHome')) {
checkWeb('#web', webServer);
}
$(elem).select();
});
}
function uploadFile(file, callback) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = function(e) {
socket.emit('writeFile', 'sayit.' + instance, 'tts.userfiles/' + file.name, e.target.result, function () {
if (callback) callback(file.name);
});
};
// Read in the image file as a data URL.
reader.readAsArrayBuffer(file);
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
$('#drop_indcator').hide();
// files is a FileList of File objects. List some properties.
var count = 0;
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
$('#files').val('');
return;
}
if (f.name === 'say.mp3') {
showMessage(_('Name say.mp3 is reserved'));
$('#files').val('');
return;
}
count++;
uploadFile(f, function (name) {
count--;
if (!count) {
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(name).trigger('change');
$('#announce').select();
$('#files').val('');
});
}
});
}
}
function handleFileSelectDrop(evt) {
$('#drop_indcator').hide();
evt.stopPropagation();
evt.preventDefault();
var files = evt.dataTransfer.files; // FileList object.
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
if (f.size > 1024 * 1024) {
showMessage(_('File %s is too big. Maximum 1MB', escape(f.name)));
return;
}
console.log(escape(f.name));
}
}
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
$('#drop_indcator').show();
}
// the function loadSettings has to exist ...
function load(settings, onChange) {
if (!settings) return;
if (!settings.player) {
settings.player = 'mpg321';
}
gOnChange = onChange;
$('#play').on('click', function () {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function(buffer) {
//console.log(buffer);
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start(0);
}, function(err) {
console.log(err);
});
}
});
});
var $t = $('#type');
for (name in sayitOptions) {
if (sayitOptions.hasOwnProperty(name)) {
$t.append('<option value="' + name + '">' + sayitOptions[name].name + '</option>');
}
}
$('#search').on('click', function () {
fillGoogleHome();
});
getIsAdapterAlive(function (isAlive) {
if (isAlive || common.enabled) {
$('#search').removeClass('disabled');
if (settings.type === 'googleHome') {
fillGoogleHome();
}
} else {
$('#search').addClass('disabled').attr('title', _('Adapter must be enabled'));
}
});
var $e = $('#engine');
for (var name in sayitEngines) {
if (sayitEngines.hasOwnProperty(name)) {
$e.append('<option value="' + name + '">' + sayitEngines[name].name + '</option>');
}
}
for (var key in settings) {
if (settings.hasOwnProperty(key)) {
var $val = $('#' + key + '.value');
if ($val.attr('type') === 'checkbox') {
$val.prop('checked', settings[key]);
} else {
$val.val(settings[key]);
}
}
}
$('.value').on('change', function () {
var key = $(this).attr('id');
if (key === 'auth') {
if ($('#auth').prop('checked')) {
$('#secure').prop('checked', true);
}
} else
if (key === 'type' || key === 'engine') {
showHideSettings();
var type = $('#type').val();
if ((type === 'sonos') ||
(type === 'chromecast') ||
(type === 'mpd') ||
(type === 'googleHome')) {
checkWeb('#web');
$('#announce').val('').trigger('change');
showHideSettings();
} else {
showHideSettings();
}
} else
if (key === 'announce') {
showHideSettings();
if ($(this).val()) {
socket.emit('readFile', 'sayit.' + instance, 'tts.userfiles/' + $('#announce').val(), function (err, data) {
if (typeof AudioContext !== 'undefined') {
context = new AudioContext();
context.decodeAudioData(data, function (buffer) {
$('#annoDuration').val(Math.ceil(buffer.duration));
}, function (err) {
console.log(err);
});
}
});
}
}
if (key === 'web') {
checkWeb('#web');
}
onChange();
}).keyup(function() {
$(this).trigger('change');
});
if (!settings.engine) {
settings.engine = systemLang;
$('#engine').val(systemLang).trigger('change');
}
if (!settings.instance) {
settings.instance = 'FFFFFFFF';
$('#instance').val(settings.instance).trigger('change');
}
fillSonosDevices('#device', settings.device);
fillChromecastDevices('#cDevice', settings.cDevice);
fillWebServices('#web', settings.web, settings.type, settings.webServer);
fillMpdDevice('#mpd_device', settings.mpd_device);
if ((settings.type === 'sonos') ||
(settings.type === 'chromecast') ||
(settings.type === 'mpd') ||
(settings.type === 'googleHome')){
$('.announce').hide();
if (settings.announce) {
$('#announce').val('').trigger('change');
}
}
// Read names of files for gong
socket.emit('readDir', 'sayit.' + instance, 'tts.userfiles', function (err, dir) {
var text = '<option value="">' + _('none') + '</option>';
if (dir) {
for (var i = 0; i < dir.length; i++) {
if (dir[i].isDir) continue;
text += '<option value="' + dir[i].file + '">' + dir[i].file + '</option>';
}
}
$('#announce').html(text).val(settings.announce);
showHideSettings();
});
getAdapterInstances('cloud', function (list) {
if (list) {
var text = '';
for (var i = 0; i < list.length; i++) {
var id = list[i]._id.substring('system.adapter.'.length);
text += '<option value="' + id + '" ' + (id === settings.cloud ? 'selected' : '') + '>' + id + '</option>';
}
if (text) $('#cloud').html(text);
}
});
$('#voice').val(settings.voice);
var w = $('#engine').width();
$('#announce').css({width: w});
$('#type').css({width: w});
document.getElementById('files').addEventListener('change', handleFileSelect, false);
showHideSettings();
if (!$('#announce').val()) $('#play').addClass('disabled');
if (typeof AudioContext === 'undefined') $('#play').hide();
var dropZone = document.getElementById('drop_zone');
if (dropZone) {
dropZone.addEventListener('dragover', handleDragOver, false);
dropZone.addEventListener('drop', handleFileSelect, false);
/*dropZone.addEventListener('dragend', function () {
$(this).css({background: 'white'});
console.log('dragend');
return false;
}, false);
dropZone.addEventListener('dragstart', function () {
console.log('dragstart');
}, false);
dropZone.addEventListener('dragleave', function () {
$(this).css({background: 'white'});
}, false);
dropZone.addEventListener('dragenter', function () {
$(this).css({background: 'gray'});
}, false);*/
}
onChange(false);
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if (obj.engine && sayitEngines[obj.engine].engine === 'yandex') {
if (!obj.key) {
showMessage(_('API Key is not set!'));
return;
}
} else
if (obj.engine && sayitEngines[obj.engine].engine === 'polly') {
if (!obj.accessKey || !obj.secretKey) {
showMessage(_('API Key is not set!'));
return;
}
if (!obj.region) {
showMessage(_('AWS Region is not set!'));
return;
}
}
if (obj.type === 'googleHome') {
obj.cache = false;
}
callback(obj);
}
</script>
</head>
<body>
<div class="m adapter-container" ondragover="return false" ondrop="return false">
<div class="row">
<div class="col s12">
<div class="row">
<div class="col s6">
<img src="sayit.png" class="logo">
</div>
</div>
<div class="row">
<div class="input-field col s6 m4">
<select class="value" id="type"></select>
<label class="translate" for="type">Type:</label>
</div>
<div class="input-field col s6 m4" id="tr_engine" >
<select class="value" id="engine"></select>
<label class="translate" for="engine">Language:</label>
</div>
</div>
<div class="row system">
<div class="input-field col s6 m4">
<select class="value" id="player" >
<option value="mpg321">mpg321</option>
<option value="omxplayer">omxplayer</option>
</select>
<label for="player"><span class="translate">Linux player:</span><span class="translate" style="padding-left: 10px; font-size: 10px">Ignore for non linux OS</span></label>
</div>
<div class="input-field col s6 m4 l2">
<input class="value" id="command"/>
<label class="translate" for="command">System command:</label>
</div>
</div>
<div class="row announceValue">
<div class="input-field col s6 m4">
<select class="value" id="announce"></select>
<label class="translate" for="announce">Announce:</label>
&nbsp;<a class="waves-effect waves-light btn translate" id="play">play</a>
</div>
<div class="file-field input-field col s6 m4">
<div class="btn">
<span class="translate">File</span>
<input type="file" accept=".mp3,audio/*" id="files" name="files[]" multiple />
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text">
</div>
</div>
</div>
<div class="row announce">
<div class="input-field col s4 m4 l2">
<input class="value" id="annoTimeout" type="number" min="0" max="1000" />
<label class="translate" for="annoTimeout">Announce timeout (sec):</label>
</div>
<div class="input-field col s4 m4 l2">
<input class="value" id="annoDuration" type="number" min="0" max="99"/>
<label class="translate" for="annoDuration">Announce length (sec):</label>
</div>
<div class="input-field col s4 m4 l2">
<input class="value" id="annoVolume" type="number" min="0" max="100"/>
<label class="translate" for="annoVolume">Announce volume (%):</label>
</div>
</div>
<div class="row">
</div>
<div class="row variable">
<div class="input-field col s6 m4" id="tr_cache">
<input class="value" id="cache" type="checkbox" />
<label class="translate" for="cache">Cache:</label>
</div>
<div class="input-field col s6 m4" id="tr_cacheExpiryDays" >
<input class="value" id="cacheExpiryDays" size="5" />
<label class="translate" for="tr_cacheExpiryDays">Cache-Expiry:</label>
</div>
</div>
<div id="tr_server" class="row variable">
<div class="input-field col s6 m4 l2">
<input class="value" id="server"/>
<label class="translate" for="server">Server:</label>
<select class="value googleHome" id="googleHome">
<option value="" class="translate">none</option>
</select>
<a class="waves-effect waves-light btn translate googleHome" style="font-size: 10px; margin-left: 10px;" id="search">Browse</a>
</div>
</div>
<div id="tr_port" class="row variable">
<div class="input-field col s6 m4 l2">
<input class="value" id="port" size="5" maxlength="5"/>
<label class="translate" for="port">Port:</label>
</div>
</div>
<div class="row variable">
<div id="tr_user" class="input-field col s6 m4 l2">
<input class="value" id="user" />
<label class="translate" for="user">User:</label>
</div>
<div id="tr_pass"class="input-field col s6 m4 l2">
<input class="value" id="pass" />
<label class="translate" for="pass">Password:</label>
</div>
</div>
<div id="tr_instance" class="row variable">
<div class="input-field col s6 m4 l2">
<label class="translate" for="instance">Browser instance:</label>
<input class="value" id="instance" />
</div>
</div>
<div id="tr_device" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="device">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="device">Device:</label>
</div>
</div>
<div id="tr_cDevice" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="cDevice">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="cDevice">Device:</label>
</div>
</div>
<div id="tr_mpd_device" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="mpd_device">
<option value="" class="translate">All</option>
</select>
<label class="translate" for="mpd_device">Device:</label>
</div>
</div>
<div id="tr_web" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="web"></select>
<label class="translate" for="web">Web instance:</label>
</div>
</div>
<div id="tr_webServer" class="row variable">
<div class="input-field col s6 m4 l2">
<select class="value" id="webServer"></select>
<label class="translate" for="webServer">Web server IP:</label>
</div>
</div>
<div id="tr_voice" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="voice"></select>
<label class="translate" for="voice">Voice:</label>
</div>
</div>
<div id="tr_key" class="row engine" >
<div class="input-field col s6 m4 l2">
<input class="value" id="key" type="text"/>
<label class="translate" for="key">API Key:</label>
</div>
</div>
<div id="tr_emotion" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="emotion"></select>
<label class="translate" for="emotion">Emotion:</label>
</div>
</div>
<div id="tr_drunk" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="drunk"/>
<label class="translate" for="drunk">Drunk:</label>
</div>
</div>
<div id="tr_ill" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="ill"/>
<label class="translate" for="ill">Ill:</label>
</div>
</div>
<div id="tr_robot" class="row engine" >
<div class="input-field col s6 m4 l2">
<input type="checkbox" class="value" id="robot"/>
<label class="translate" for="robot">Robot:</label>
</div>
</div>
<div id="tr_accessKey" class="row engine" >
<div class="input-field col s6 m4 l2">
<label class="translate" for="accessKey">Access Key:</label>
<input class="value" id="accessKey" type="text"/>
</div>
</div>
<div id="tr_secretKey" class="row engine" >
<div class="input-field col s6 m4 l2">
<label class="translate" for="secretKey">Secret Key:</label>
<input class="value" id="secretKey" type="password"/>
</div>
</div>
<div id="tr_region" class="row engine" >
<div class="input-field col s6 m4 l2">
<input class="value" id="region" type="text"/>
<label class="translate" for="region">AWS Region:</label>
</div>
</div>
<div id="tr_cloud" class="row engine" >
<div class="input-field col s6 m4 l2">
<select class="value" id="cloud">
<option value="" class="translate">Install first cloud adapter</option>
</select>
<label class="translate" for="cloud">Cloud instance:</label>
</div>
</div>
</div>
</div>
<div id="drop_zone" style="display: none" class="translate">place here</div>
</div>
</body>
</html>