diff --git a/server/Server.java b/server/Server.java index e9c3031..08aa50e 100644 --- a/server/Server.java +++ b/server/Server.java @@ -1,169 +1,3534 @@ +/* + +PVA is coded by Marius Schwarz since 2021 + +This software is free. You can copy it, use it or modify it, as long as the result is also published on this condition. +You only need to refer to this original version in your own readme / license file. + +*/ + package server; +import java.util.*; import java.io.*; -import java.util.Date; -import java.net.ServerSocket; -import java.net.Socket; -import javax.net.*; -import javax.net.ssl.*; -import java.net.InetAddress; -import java.security.*; -import java.security.cert.*; -import io.Dos; - -public class Server { - - private boolean debug = false; - - private final SSLServerSocket server; - private SSLContext sslContext; - Dos dos = new Dos(); - - Thread ich; - PVA pva; - - public Server( int port , PVA pva ) throws IOException { - - this.pva = pva; - server = makeSSLSocket(port); -// server.setNeedClientAuth(true); // in case you wanne build a trustchain for your clients, you need special signed clientcerts from your servercert && a working trustmanager. - } +import io.*; +import hash.*; +import data.*; +import java.nio.file.*; +import java.util.regex.*; +import com.mpatric.mp3agic.*; +import server.Server; +import plugins.Plugins; +import utils.Tools; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; - public Server( PVA pva ) throws IOException { - - this.pva = pva; - server = makeSSLSocket(pva); - } +public class PVA { + + static String keyword = "carola"; - private ServerSocketFactory getFactory() { + static long debug = 0; + + static public TwoKeyHash config = new TwoKeyHash(); + static TwoKeyHash alternatives = new TwoKeyHash(); + static public TwoKeyHash texte = new TwoKeyHash(); + static TwoKeyHash context = new TwoKeyHash(); + static Vector reactions = new Vector(); + static Vector commands = new Vector(); + static Vector contacts = new Vector(); + static Vector mailboxes = new Vector(); + static StringHash timers = new StringHash(); + static Vector categories = new Vector(); + static TimerTask tt; + static IMAPTask it; + static SearchTask st; + static MetacacheTask mt; + static Plugins pls; + static Server server; + static AIMessages aimsgs = new AIMessages(); + + static String text = ""; + static String text_raw = ""; + static int mbxid = 1; + static boolean reaction = false; + static Dos dos = new Dos(); + static String[] za = null; + static int c = 0; + static String metadata_enabled = ""; - try { - String p = dos.readFile(System.getenv("HOME").trim()+"/.config/pva/kspass.txt").trim(); - - // for some reason, toCharArray() didn't work, so we do it manually: + static void log(String x) { System.out.println(x); } + static String[] timedata; + + static String makeDate(long time) { + + Calendar d = Calendar.getInstance(); + d.setTime( new Date(time) ); + + String[] months = config.get("conf","months").split(":"); - char[] password = new char[p.length()]; - for(int i=0;i 1 ) { - sslContext = SSLContext.getInstance("TLS"); + String key = data[0].replaceAll("\"",""); + String value = data[1]; + +// log("key="+ key +"\nvalue="+ value ); - KeyStore ks = KeyStore.getInstance("JKS"); - FileInputStream fis = null; - try { - fis = new FileInputStream( System.getenv("HOME").trim()+"/.config/pva/.keystore"); - ks.load(fis, password); - } finally { - if (fis != null) fis.close(); - } + if ( key.endsWith("\"") ) key = key.substring(0,key.indexOf("\"")-1); + if ( value.endsWith("\"") ) value = value.substring(0,value.indexOf("\"",1)-1); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509", "SunJSSE"); - kmf.init(ks,password); +// log("key="+ key +"\nvalue="+ value ); + + if ( key.equals("response") || key.equals("content") ) { + answere = value.substring(1).replaceAll("\\n","\n"); + aimsgs.addMessage(new AIMessage("assistant", model, answere )); + } + } + if ( answere == null ) answere = ""; + } + } + + return answere; + + } + + // Speakers tend to pronounce 1746 as a year, not 1.746. + // we translate this to a numberformat most speakers understand better. + + static String makeNumber(long c) { + + String b = ""; + String number = ""+c; + for(int i=number.length();(i-3)>0;i=i-3) { + // log("i="+i); + b = "."+ number.substring(i-3,i) + b; + // log("b="+b); + } + int modulo = number.length()%3; + if ( modulo != 0 ) { + b = number.substring(0, modulo)+ b; + } else if ( b.length() > 0 ) { + b = b.substring(1); + } else b = number; + + return b; + } + + static void exec(String cmd) throws IOException { + exec(cmd, false ); + } + + static void exec(String cmd, boolean wait) throws IOException { +// log( cmd ); + try { + Process p = Runtime.getRuntime().exec( cmd ); + if ( wait ) { + p.waitFor(); + } + } catch (Exception e) { + // we don't care + } + reaction = true; + } - sslContext.init(kmf.getKeyManagers(), - null, - new java.security.SecureRandom() ); + static void exec(String[] cmds) throws IOException { + exec( cmds, false ); + } - return sslContext.getServerSocketFactory(); - + static public void exec(String[] cmds, boolean wait ) throws IOException { + if ( cmds == null || cmds.length == 0 ) { + log("EMPTY Command given to Exec()"); + return; + } + String x = ""; + for(String cmd : cmds) { + x += cmd+"#|#"; + if (debug > 2 ) log( "argument:"+cmd ); + if ( cmd == null ) { + log("exec():Illegal Argument NULL detected"); + reaction = false; + return; + } + if ( cmd.isEmpty() && cmds.length == 1 ) { + // this can happen, if i.e. raisevolume is executed, but not configured! + if ( debug > 2 ) log("exec():empty Argument \"\" detected"); + reaction = false; + return; + } + } + try { + Process p = Runtime.getRuntime().exec( cmds ); + if ( wait ) { + p.waitFor(); + } } catch (Exception e) { - // nothing we can do about this - return null; + // we don't care + log("we had a crash in exec("+x+"):\n"+e); + e.printStackTrace(); } + reaction = true; + } + + static boolean wort(String gesucht) { + +// log( "wort: text="+ text +" gesucht="+ gesucht); + + if ( text.contains(gesucht.toLowerCase()) ) return true; + return false; } - private SSLServerSocket makeSSLSocket(int port) { + static boolean oder(String gesucht) { + String[] args = gesucht.toLowerCase().split("\\|"); + for(String arg: args) { +// System.out.println(text +" arg="+arg); + if ( text.contains( arg ) ) + return true; + + } + return false; + } - try { - ServerSocketFactory ssocketFactory = getFactory(); - SSLServerSocket socket = (SSLServerSocket) ssocketFactory.createServerSocket(port); - return socket; + static boolean und(String gesucht) { + String[] args = gesucht.toLowerCase().split("\\|"); + for(String arg: args) { + if ( ! text.contains( arg ) ) { + return false; + } + } + return true; + } - } catch (Exception e) { - System.out.println(e); - e.printStackTrace(); - } - return null; - } + static String replaceVariables(String term) { - private SSLServerSocket makeSSLSocket(PVA pva) { - - try { - ServerSocketFactory ssocketFactory = getFactory(); - - int port = Integer.parseInt( pva.config.get("network","port") ); - int backlog = 200; - - SSLServerSocket socket = (SSLServerSocket) ssocketFactory.createServerSocket(port, backlog, InetAddress.getByName( pva.config.get("network","bindaddress") ) ); - return socket; - - } catch (Exception e) { - System.out.println(e); - e.printStackTrace(); - } - return null; - } +// log("LOOP-Start: "+ term ); + String rpl = ""; + String srx = Tools.zwischen( term, "{","}"); + if ( srx != null && srx.length() > 0 ) { +// log( srx ); - public void log(String x) { - System.out.println((new Date())+": Server: "+x); - } + String[] rargs = srx.split(":"); + // we need at least 3 Arguments to this or it won't work - public void interrupt() { - Thread.currentThread().interrupt(); - } + if ( rargs.length == 3 ) { + if ( rargs[0].equals("config") ) + rpl = config.get( rargs[1], rargs[2] ); + + if ( rargs[0].equals("texte") ) + rpl = texte.get( rargs[1], rargs[2] ); - protected void startServing() throws Exception { + if (rpl == null ) rpl = ""; + +// log("replacement: |"+ rpl +"|" ); - ich = Thread.currentThread(); + + } else rpl = ""; // avoid unsolvable loop-of-death + + } else rpl = ""; // avoid unsolvable loop-of-death - while ( true ) { - if ( Thread.currentThread().isInterrupted() ) { - log("Exiting"); - return; - } - try { + term = term.replace("{"+srx+"}",rpl.replace("%VOICE", config.get("conf","lang_short")) ); + +// log( term ); + + return term; + } + + static void handleMediaplayer(String servicename, String cmd) { + + try { + String vplayer = dos.readPipe( config.get("mediaplayer","status").replaceAll("", servicename).replaceAll( config.get("conf","splitter")," ") ); + + if ( debug > 1 ) log( "handleMediaplayer: "+ config.get("mediaplayer","status").replaceAll("", servicename).replaceAll( config.get("conf","splitter")," " ) +"\nResult:"+ vplayer ); + + if ( ! vplayer.trim().isEmpty() && vplayer.trim().contains("Playing") ) { + + // variant double 0.8 + + Double f = 0.5; + + String volume = dos.readPipe( config.get("mediaplayer","getvolume").replaceAll("", servicename).replaceAll( config.get("conf","splitter")," ") ); + + if ( !volume.trim().isEmpty() && volume.contains("double") ) { + volume = volume.substring( volume.indexOf("double")+6 ).trim(); + f = Double.parseDouble( volume ); + + if ( cmd.equals("DECVOLUME") ) { + f = f - 0.25; + if ( f < 0.0 ) f = 0.0; + + dos.readPipe( config.get("mediaplayer","lowervolume").replaceAll("", servicename).replaceAll("", ""+ f ).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + + if ( cmd.equals("INCVOLUME") ) { + f = f + 0.25; + if ( f > 2.0 ) f = 2.0; + + dos.readPipe( config.get("mediaplayer","raisevolume").replaceAll("", servicename).replaceAll("", ""+ f ).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + + if ( cmd.equals("DECVOLUMESMALL") ) { + f = f - 0.05; + if ( f < 0.0 ) f = 0.0; + dos.readPipe( config.get("mediaplayer","lowervolume").replaceAll("", servicename).replaceAll("", ""+ f ).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + + if ( cmd.equals("INCVOLUMESMALL") ) { + f = f + 0.05; + if ( f > 2.0 ) f = 2.0; + + dos.readPipe( config.get("mediaplayer","raisevolume").replaceAll("", servicename).replaceAll("", ""+ f ).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + } + } + + + if ( cmd.equals("VIDEOPLAYBACKPLAY") ) { + exec(config.get("mediaplayer","play").replaceAll("", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEOPLAYBACKSTOP") ) { + exec(config.get("mediaplayer","stop").replaceAll("", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEOPLAYBACKPAUSE") ) { + + exec(config.get("mediaplayer","pause").replaceAll("", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEOPLAYBACKTOGGLE") ) { + exec(config.get("mediaplayer","toggle").replaceAll("", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEONEXTTRACK") ) { + exec(config.get("mediaplayer","nexttrack").replaceAll("", servicename).split(config.get("conf","splitter"))); + if ( wort("übernächstes") ) exec(config.get("mediaplayer","nexttrack").replaceAll("", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEOPREVTRACK") ) { + + exec(config.get("mediaplayer","lasttrack").replaceAll("", servicename).split(config.get("conf","splitter"))); + + if ( wort("vorletztes") ) exec(config.get("mediaplayer","lasttrack").replaceAll("", servicename).split(config.get("conf","splitter")));; + } + + if ( cmd.equals("VIDEONTRACKSFORWARDS") ) { + + for(int i=0;i", servicename).split(config.get("conf","splitter"))); + + } + + if ( cmd.equals("VIDEONTRACKSBACKWARDS") ) { + + for(int i=0;i", servicename).split(config.get("conf","splitter"))); + } + + if ( cmd.equals("VIDEOPLAYBACKFULLSCREENON") ) { + + dos.readPipe( config.get("mediaplayer","fullscreen").replaceAll("", servicename).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + + if ( cmd.equals("VIDEOPLAYBACKFULLSCREENOFF") ) { + dos.readPipe( config.get("mediaplayer","windowmode").replaceAll("", servicename).replaceAll(config.get("conf","splitter")," ") ); + reaction = true; + } + + + } catch (IOException e) { + System.out.println(e.getMessage()); + } + + + } + + static String formatMetadata(String filename, ID3v1 id3v1Tag ) { + + if ( id3v1Tag != null ) { + + return ( filename + + config.get("conf","splitter") + id3v1Tag.getTrack() + + config.get("conf","splitter") + id3v1Tag.getArtist() + + config.get("conf","splitter") + id3v1Tag.getTitle() + + config.get("conf","splitter") + id3v1Tag.getAlbum() + + config.get("conf","splitter") + id3v1Tag.getYear() + + config.get("conf","splitter") + id3v1Tag.getGenre() + + config.get("conf","splitter") + id3v1Tag.getGenreDescription() + + config.get("conf","splitter") + id3v1Tag.getComment() ).replaceAll("(\n|\r)"," ") + "\n"; + + } else return ""; + + } + + static String formatMetadata(String filename, ID3v2 id3v2Tag ) { + + if ( id3v2Tag != null ) { + return ( filename.replace("'","\'") + + config.get("conf","splitter") + id3v2Tag.getTrack() + + config.get("conf","splitter") + id3v2Tag.getArtist() + + config.get("conf","splitter") + id3v2Tag.getTitle() + + config.get("conf","splitter") + id3v2Tag.getAlbum() + + config.get("conf","splitter") + id3v2Tag.getYear() + + config.get("conf","splitter") + id3v2Tag.getGenre() + + config.get("conf","splitter") + id3v2Tag.getGenreDescription() + + config.get("conf","splitter") + id3v2Tag.getComment() + + config.get("conf","splitter") + id3v2Tag.getComposer() + + config.get("conf","splitter") + id3v2Tag.getPublisher() + + config.get("conf","splitter") + id3v2Tag.getOriginalArtist() ).replaceAll("(\n|\r)"," ") + "\n" ; + + } else return ""; + + } + + static String createMetadata(String start) { + + String[] files = start.split(config.get("conf","splitter")); + StringBuffer data = new StringBuffer(3000000); + + TwoKeyHash tk = new TwoKeyHash(); + tk.put("proc","counter","0"); + + final int max = 200; + + if ( files != null) { + for(int i =0; i < files.length; i++ ) { + // System.out.println("processing file "+ entries[i].toString() +" matches ("+type+")$ ??? "+ entries[i].toString().matches(".*("+type+")$") ); + if ( files[i].toLowerCase().matches(".*mp3$") ) { + + + try { + if ( Integer.parseInt( tk.get("proc","counter") ) > max ) do { + Thread.sleep(50L); + } while ( Integer.parseInt( tk.get("proc","counter") ) > max ); + + if ( debug > 4 ) log("["+ tk.get("proc","counter") +" len="+ data.length() +"] Analyse mp3 "+i+"/"+ files.length+" ... "+ files[i] ); + new AnalyseMP3(data, files[i], tk ).start(); + } catch ( Exception e ) { + log(e.toString()); + e.printStackTrace(); + } + + } + } + } + + if ( debug > 4 ) log("waiting for sub-processes to finish"); + try { + int count = 0; + if ( Integer.parseInt( tk.get("proc","counter") ) > 0 ) do { + StringHash finfo = tk.get("files"); + Enumeration en = finfo.keys(); + if ( debug > 4 ) log("waiting for processes to finish.. counter="+ tk.get("proc","counter")); + count = 0; + while ( en.hasMoreElements() ) { + String key = (String)en.nextElement(); + String value = finfo.get( key ); + if ( value.equals("0") ) { + if ( debug > 4 ) log( key +"="+ value); + count ++; + } + } - Socket socket = server.accept(); - socket.setKeepAlive(true); - - DataInputStream in = new DataInputStream(socket.getInputStream()); + + Thread.sleep(100L); + } while ( Integer.parseInt( tk.get("proc","counter") ) > 0 && count > 0 ); + + } catch ( Exception e ) { + log(e.toString()); + e.printStackTrace(); + } + + return data.toString(); + } + + static String[] buildFileArray(String str,String rpl) { + + String[] x = str.split(" "); + String[] y = String.join("\\\\ ",rpl.split(" ")).split(config.get("conf","splitter")); + String[] z = new String[x.length-1+y.length]; + + int idx = 0; + for(int i=0;i childprocess: "+ ich.isInterrupted() ); - break; - } - - byte[] buffer = new byte[50000]; - int len = 0; - do { + static AppResult __searchExternalApps(String path,String suchwort) { - len = in.read(buffer); - if ( len > 0 ) { - text.append( new String( buffer, 0, len) ); + // we want to prefer matches with the Nameentry above the keywordentries, so we need to track both matches seperatly + + NumericTreeSort namematches = new NumericTreeSort(); + NumericTreeSort keywordmatches = new NumericTreeSort(); + + if ( debug > 2 ) log("_searchExternalApps: searchpath="+ path +" searchterm="+ suchwort); + + File file = new File(path); + File[] entries = file.listFiles(); + suchwort = suchwort.trim().toLowerCase(); + + if ( debug > 3 ) log("_searchExternalApps: Name= Name["+config.get("conf","lang")+"]= Name["+config.get("conf","lang_short")+"]="); + + if ( entries != null ) { + for(int i =0; i < entries.length; i++ ) { + if ( debug > 2 ) log("_searchExternalApps: processing file "+ entries[i].toString() ); + try { + if ( entries[i].isDirectory() && !entries[i].getName().startsWith(".") ) { + + // directoryname contains searchword(s) so we add it entirely + // log("add "+ entries[i].getCanonicalPath() +" mit *"); + + // We have to assume, that the result is the best result we can get from this subdirectory and import it in our active NumericTreeSort + // if it's the best we ever get, it will be on top of all results in our actual directory also + + AppResult r = __searchExternalApps( entries[i].getCanonicalPath() , suchwort); + if ( r != null ) { + if ( !r.namematches.isEmpty() ) { + namematches.add( r.namerelevance, r.namematches); + } + if ( !r.keywordmatches.isEmpty() ) { + keywordmatches.add( r.keywordrelevance, r.keywordmatches); + } } - } while ( len > 0 ); + } else if ( entries[i].toString().toLowerCase().endsWith(".desktop") ) { + + String[] content = dos.readFile( entries[i].getCanonicalPath() ).split("\n"); - try { + // those hitflags are required because the order of "Exec=" and i.e. "Name=" is not fix. The flags indicate a match, even if Exec= wasn't found jet. + // they also represent the relevance factor of that entry > -1 + long nhit = -1; + long khit = -1; + String app_exe = ""; + for(String line: content) { + line=line.trim(); + if ( line.startsWith("Exec=") ) { + app_exe = line.substring( line.indexOf("=")+1 ); + + } + + if ( debug > 3 ) log("_searchExternalApps: "+ entries[i].toString()+":"+ line ); + + if ( line.startsWith("Name=") || line.startsWith("Name["+config.get("conf","lang")+"]=") || line.startsWith("Name["+config.get("conf","lang_short")+"]=") ) { + String name = line.substring( line.indexOf("=")+1 ); + if ( name.toLowerCase().contains(suchwort) ) { + nhit = suchwort.length()*100/name.length(); // we can't know, if "Exec=" stands before or after Name*= in the desktopfile! + } + if ( debug > 3 ) log("_searchExternalApps: name="+name.toLowerCase()+" suchwort="+suchwort+" hit="+ nhit ); + } + if ( line.startsWith("Keywords=") || line.startsWith("Keywords["+config.get("conf","lang")+"]=") || line.startsWith("Keywords["+config.get("conf","lang_short")+"]=") ) { + String name = line.substring( line.indexOf("=")+1 ).toLowerCase(); + String[] keys = name.split(";"); + for(String key: keys) { + + if ( key.toLowerCase().contains(suchwort) || key.toLowerCase().equals( suchwort ) ) { + khit = suchwort.length()*100/name.length(); // we can't know, if "Exec=" stands before or after Name*= in the desktopfile! + } + } + } + + // if line starts with [ we hit a desktop file with multiply entries in it and need to reset the search. + + if ( line.startsWith("[") ) { + if ( nhit > -1 ) { + if ( debug > 2 ) log( "found namematch for "+ suchwort +" in " + entries[i].toString()); + + if ( ! app_exe.trim().isEmpty() ) + namematches.add( nhit , app_exe.trim() ); + + nhit = -1; + } + if ( khit > -1 ) { + if ( debug > 2 ) log( "found keywordmatch for "+ suchwort +" in " + entries[i].toString()); + + if ( ! app_exe.trim().isEmpty() ) + keywordmatches.add( khit , app_exe.trim() ); + + khit = -1; + + } + } + } + + if ( nhit > -1 ) { + // if the app is already in the list, ignore it. + + if ( debug > 2 ) log( "found namematch for "+ suchwort +" in " + entries[i].toString()); + + if ( ! app_exe.trim().isEmpty() ) + namematches.add( nhit , app_exe.trim() ); + + nhit = -1; + } + if ( khit > -1 ) { + if ( debug > 2 ) log( "found keywordmatch for "+ suchwort +" in " + entries[i].toString()); + + if ( ! app_exe.trim().isEmpty() ) + keywordmatches.add( khit , app_exe.trim() ); + + khit = -1; + } + } + } catch(IOException e) { + System.out.println(e.getMessage()); + } + } + } + + String n = ""; + String k = ""; + long nr = 0; + long kr = 0; + + // If we have matches, the try-part does succeed, if it fails, we did not have a match and we use "" as default + // n and nr depend on each other, so it's 100% safe to assume that, if one fails, both fail. + + try { String[] r = namematches.getValues_internal().split(","); n = r[r.length-1]; nr = Long.parseLong( namematches.getKeys_internal().split(",")[r.length-1] ); } catch (Exception e) { } + try { String[] r = keywordmatches.getValues_internal().split(","); k = r[r.length-1]; kr = Long.parseLong( keywordmatches.getKeys_internal().split(",")[r.length-1] ); } catch (Exception e) { } + + // we now return our best match result. + + return new AppResult( n, k, nr, kr ); + } + + static String _searchExternalApps(String path,String suchwort) { + + // We prefer App Matches from "Name=" entries above "Keywords=" matches + + AppResult r = __searchExternalApps(path,suchwort); + if ( r != null ) { + + if ( debug > 2 ) log("_searchExternalApps: "+ r.namematches +" or "+ r.keywordmatches); + + if ( ! r.namematches.isEmpty() ) { + return r.namematches; + } + return r.keywordmatches; + + } + return ""; + } + + public static String getDesktop() { + return System.getenv("DESKTOP_SESSION").trim(); + } + + public static String getHome() { + return System.getenv("HOME").trim(); + } + + static String searchExternalApps(String suchwort) { + + String filename =""; + filename += _searchExternalApps("/usr/share/applications/", suchwort ); + if ( filename.isEmpty() ) + // TODO: Add more folderselection based on languages. Is there a database? How does gnome knows it on First Launch? + if ( config.get("conf","lang").equals("de_DE") ) { + filename += _searchExternalApps(getHome() + "/Schreibtisch", suchwort ); + } else filename += _searchExternalApps(getHome() + "/Desktop", suchwort ); + + return filename; + } + + static String cacheSuche(String start,String suchwort,String type) { + + + suchwort = suchwort.trim().toLowerCase(); + + if ( debug > 4 ) log("cacheSuche:suchwort = "+ suchwort); + + if ( suchwort.isEmpty() ) return ""; + + String[] files = start.split(config.get("conf","splitter")); + String filename =""; + + if ( files != null ) { + for(int i =0; i < files.length; i++ ) { + if ( debug > 4 ) log("processing file "+ files[i].toString() +" matches ("+type+")$ ??? "+ files[i].toString().matches(".*("+type+")$") ); + if ( files[i].toLowerCase().endsWith(type) || files[i].toLowerCase().matches(".*("+type+")$") ) { + + if ( files[i].toLowerCase().matches( suchwort ) ) { + if ( debug > 4 ) log("cacheSuche:filename:found="+ files[i]); + filename += files[i]+config.get("conf","splitter"); + } + + } + } + } + + if ( type.contains("mp3") ) { + if ( dos.fileExists( getHome()+"/.cache/pva/cache.metadata") ) { + start = dos.readFile(getHome()+"/.cache/pva/cache.metadata"); + files = start.split("\n"); + if ( files != null ) { + for(int i =0; i < files.length; i++ ) { + if ( debug > 4 ) log("cacheSuche:mp3:metacache:search in file = "+ files[i] ); + if ( files[i].toLowerCase().matches( suchwort ) ) { + if ( debug > 4 ) log("cacheSuche:metadata:found="+ files[i]); + filename += files[i].split(config.get("conf","splitter"))[0]+config.get("conf","splitter"); + } + } + } + } + } + + return filename; + } + + static String _suche(String start,String suchwort,String type) { +/* + if (isInterrupted()) + return ""; +*/ + + File file = new File(start); + File[] entries = file.listFiles(); + String filename =""; + suchwort = suchwort.trim().toLowerCase(); + if ( suchwort.isEmpty() ) return ""; +// log(suchwort); + if ( entries != null ) { + for(int i =0; i < entries.length; i++ ) { + // System.out.println("processing file "+ entries[i].toString() +" matches ("+type+")$ ??? "+ entries[i].toString().matches(".*("+type+")$") +" searchterm="+ suchwort ); + + try { + if ( entries[i].isDirectory() && !entries[i].getName().startsWith(".") ) { + boolean found = true; + if ( suchwort.contains(" ") ) { + String[] args = suchwort.split(" "); + for( String arg : args ) { + arg = arg.trim(); + if ( !arg.isEmpty() && !entries[i].getName().toLowerCase().contains( arg ) ) found = false; + } + } else if ( !suchwort.equals("*") && !entries[i].getName().toLowerCase().contains( suchwort ) ) found = false; + + if ( found ) { + // directoryname contains searchword(s) so we add it entirely + // log("add "+ entries[i].getCanonicalPath() +" mit *"); + filename += _suche( entries[i].getCanonicalPath() , "*", type); + } else { + // continue search in this directory + // log("add "+ entries[i].getCanonicalPath() +" mit "+ suchwort); + filename += _suche( entries[i].getCanonicalPath() , suchwort, type); + } + + } else if ( entries[i].toString().toLowerCase().endsWith(type) || entries[i].toString().toLowerCase().matches(".*("+type+")$") ) { + + String name = entries[i].toString().toLowerCase().replaceAll("-"," ").replaceAll("'","\'"); + + if ( suchwort.equals("*") ) { + filename += entries[i]+config.get("conf","splitter"); + // log("545: found "+ entries[i] +" mit "+ suchwort ); + } else { + + String[] args = suchwort.split(" "); + boolean found = true; + for( String arg : args ) { + arg = arg.trim(); + // System.out.println("subsuchwort="+ arg); + if ( !arg.isEmpty() && !name.contains( " "+arg+" " ) && !name.startsWith( arg ) ) { + found = false; + } + } + if ( name.contains( suchwort ) ) found = true; + + if ( found ) { + // log("560: found "+ entries[i] +" mit "+ suchwort ); + filename += entries[i]+config.get("conf","splitter"); + } + } + + } + } catch(IOException e) { + System.out.println(e.getMessage()); + } + } + } + + return filename; + } + + public static String suche(String start,String suchwort,String type) { + + if ( start.contains(":") ) { + + String[] args = start.split(":"); + String filename = ""; + for( String path : args ) { + filename += _suche( path,suchwort,type); + } + return filename; + + } else return _suche(start,suchwort,type); + } + + static String sucheNachEmail(String name) { + + Enumeration en = contacts.elements(); + while ( en.hasMoreElements() ) { + Contact c = en.nextElement(); + if ( c.searchForName( name ) ) return c.getEmails(); + } + return ""; + } + + static String sucheNachTelefonnummer(String name) { + + Enumeration en = contacts.elements(); + while ( en.hasMoreElements() ) { + Contact c = en.nextElement(); +// log(c.getFullname()); + if ( c.searchForName( name ) ) return c.getPhones(); + } + return ""; + } + + static String einzelziffern(String nummer) { + String x =""; + for(int i=0;i", + Tools.zwischen( service, "\"","\"") ).replaceAll( config.get("conf","splitter")," ") ); + + if ( ! vplayer.trim().isEmpty() && vplayer.trim().contains("Playing") ) { + playing = false; // because a player is running. + } } + } + } + } + + if ( ! dos.readPipe( "pgrep -f "+ config.get("audioplayer","pname").replaceAll( config.get("conf","splitter")," ") ).trim().isEmpty() && !config.get("audioplayer","status").isEmpty() ) { + String[] result = dos.readPipe( config.get("audioplayer","status").replaceAll(config.get("conf","splitter")," "),true).split("\n"); + for(String x : result ) { + if ( x.contains("[paused]") ) break; + if ( x.contains("TITLE") ) { + playing = false; // because a player is running. + } + } + } + return playing; + } + + static private Command parseCommand(String textToParse) { + + // Filter out "?" , just in case a third party app sends this to the pva port. it leads to an endless loop in EXEC() and/or readPipe() + + if ( textToParse.contains("?") ) textToParse = textToParse.replace("?",""); + + text = textToParse; + + Command cf = new Command("DUMMY","","",""); // cf = commandFound + for(int i=0; i < commands.size(); i++) { + + Command cc = commands.get(i); + + if (debug > 0 ) log("matching \""+ cc.words +"\" against \""+ text +"\" match_und="+ und( cc.words ) +" match_matches="+ text.matches( ".*"+ cc.words +".*" ) +" negative="+ cc.negative+" !und="+ !und( cc.negative )); + + if ( + ( + ( !cc.words.contains(".*") && und( cc.words ) ) || + ( cc.words.contains(".*") && text.matches( ".*"+ cc.words +".*" ) ) || + ( cc.words.startsWith("REGEXP:") && text.matches( cc.words.replace("REGEXP:","") ) ) // FULL REXEXP MODE + ) + && ( cc.negative.isEmpty() || !oder( cc.negative ) ) ) { + + Vector terms = new Vector(); + + cf = cc; + + // Replace context related words i.e. the APP krita is often misunderstood in german for Kreta ( the greek island ) + // it's ok to replace it for STARTAPPS, but i may not ok to replace it i.e. in a MAP Context! + + StringHash r = context.get( cc.command ); + if ( r != null ) { + Enumeration en = r.keys(); + while ( en.hasMoreElements() ) { + String a = (String)en.nextElement(); + String b = r.get( a ); - if ( !text.equals("") ) pva.handleInput(text.toString()); +// log( "replace:"+a+" => "+b); + + text = text.replaceAll(a,b); + } + } + // make a raw copy .. it's needed for filterwords to be removed from the "search term" but still recognized as optional arguments + + text_raw = text; + + // Apply special filter i.e. for binding words like "with"/"mit" + // if filter words are defined, lets remove them now. this simplyfies processing in the actual function. + if ( ! cf.filter.isEmpty() ) { + log( "replace: ("+ cf.filter +") => "); + text = text.replaceAll("("+ cf.filter +")" , ""); + } + +// log( "replace: ("+ cf.words +") => "); + + // delete the command from the phrase + + if ( cc.words.startsWith("REGEXP:") && text.matches( cc.words.replace("REGEXP:","") ) ) { + + text = ""; + + } else if ( !cc.words.contains(".*") && und( cc.words ) ) { + // Remove via classic REGEX (arg1|arg2|arg3|...) => "" + text = text.replaceAll( "("+cf.words+")", "" ); + } else { + + // Before we remove all RegExpressions, we search for lonely " .* " , invert the result to have only the parts that are represented by the ".*" are left. + // not an easy task .. :( + + String rp = text; -// in.close(); -// out.close(); + log(rp); + + String[] parts; + if ( cf.words.endsWith(" .*") ) { + parts = (cf.words+" ").split( Pattern.quote(" .* ") ); + } else if (cf.words.startsWith(".* ")) { + parts = (" "+cf.words).split( Pattern.quote(" .* ") ); + } else parts = cf.words.split( Pattern.quote(" .* ") ); + + for(String x: parts) { + if ( !x.isEmpty() ) { + if ( rp.startsWith(x) ) { + rp=rp.replaceAll(x+" ","x:x"); + } else if ( rp.endsWith(x) ) { + rp=rp.replaceAll(" "+x,"x:x"); + } else rp = rp.replaceAll(" "+x+" ","x:x"); + if ( debug > 1 ) log("replace "+x+" : result "+ rp); + } + } + + rp = rp.trim(); + if ( rp.startsWith("x:x") ) rp = rp.substring(3); + + while ( rp.contains(" ") ) + rp = rp.replaceAll("[ ]+"," "); + + log(rp); + + String[] ra = rp.trim().split("x:x"); + for(String x: ra) + terms.add( x ); + +// log("ra.length = "+ra.length +" and Vector.size = "+ terms.size()); + + cf.terms = terms; + + // Remove REGEX + text = text.replaceAll( cf.words ,""); + } + +// log("found match: "+ cc.words +" text="+ text); + + break; + + } + } + + return cf; + + } - } catch ( IOException e ) { - log(e.toString()); - e.printStackTrace(); - } - } - } -} + static public void main(String[] args) { + + try { + String configstoload = getHome()+"/.config/pva/pva.conf"; + + NumericTreeSort ss = new NumericTreeSort(); + + String debugLevel = System.getenv("DEBUG"); + if ( debugLevel == null || debugLevel.isEmpty() ) debugLevel = "0"; + debug = Long.parseLong(debugLevel); + + File configdir = new File( getHome() + "/.config/pva/conf.d"); + File[] entries = configdir.listFiles(); + if ( entries != null ) { + for(int i =0; i < entries.length; i++ ) { + if ( !entries[i].isDirectory() && !entries[i].getName().startsWith(".") && entries[i].getName().endsWith(".conf") ) { + if ( entries[i].getName().matches("^[0-9]+-.*") ) { + ss.add( Long.parseLong(entries[i].getName().split("-")[0]), entries[i].getAbsolutePath() ); + } else ss.add( 0, entries[i].getAbsolutePath() ); + } + } + } + + if ( !ss.getValues_internal().trim().isEmpty() ) + configstoload = ss.getValues_internal().replaceAll(",",":")+ ":"+ configstoload ; + + ss.reset(); + + configdir = new File("/etc/pva/conf.d"); + entries = configdir.listFiles(); + if ( entries != null ) { + for(int i =0; i < entries.length; i++ ) { + if ( !entries[i].isDirectory() && !entries[i].getName().startsWith(".") && entries[i].getName().endsWith(".conf") ) { + if ( entries[i].getName().matches("^[0-9]+-.*") ) { + ss.add( Long.parseLong(entries[i].getName().split("-")[0]), entries[i].getAbsolutePath() ); + } else ss.add( 0, entries[i].getAbsolutePath() ); + } + } + } + + if ( !ss.getValues_internal().trim().isEmpty() ) + configstoload = ss.getValues_internal().replaceAll(",",":")+ ":"+ configstoload ; + + String[] cfiles = configstoload.split(":"); + for(String conffile: cfiles) { + // read in config, if no custom config is present, load defaults. + String[] conflines = null; + + if ( debug > 0 ) log( "load config: "+ conffile ); + + if ( dos.fileExists( conffile ) ) { + conflines = dos.readFile(conffile).split("\n"); + } else conflines = dos.readFile("./pva.conf.default").split("\n"); + + // our config is a two dimentional array + // in PHP this would look like $config[$key1][$key2] = $value + + for(String line : conflines) { + if ( !line.trim().startsWith("#") && !line.trim().isEmpty() && line.contains(",") && line.contains(":") ) { + try { + String[] level1 = line.split(":",2); + String[] level2 = level1[1].trim().replaceAll("^\"","").replaceAll("\"$","").trim().split("\",\""); + + // UPS! the above construct replaces |"key",""| into => |key","| which splits into just "key" with no value left, that's why we need to check for shorter Level2-Stringarrays + + if ( level1[0].trim().equals("alternatives") ) { + + alternatives.put(level2[0].trim() , level2[1].trim() , level2[2].trim()); + + } else if ( level1[0].trim().equals("text") ) { + + texte.put(level2[0].trim() , level2[1].trim() , level2[2].trim()); + + } else if ( level1[0].trim().equals("contextreplacements") ) { + + if ( level2.length == 3 ) { + context.put( level2[0].trim() , level2[1].trim() , level2[2].trim() ); + } else context.put( level2[0].trim() , level2[1].trim() , "" ); + + } else if ( level1[0].trim().equals("reaction") ) { + + if ( level2.length == 3 ) { + + // overwrite "old" values by deleting them before adding the exact same combination + // this approach has limits, but it will work in 99% of cases. + + if ( conffile.contains("-overwrite") ) for(int i=0; i < reactions.size(); i++) { + Reaction r = (Reaction)reactions.get(i); + if ( r.positives.equals( level2[0].trim() ) && r.negatives.equals( level2[1].trim() ) ) { + reactions.remove( r ); + } + } + reactions.add( new Reaction( level2[0].trim() , level2[1].trim() , level2[2].trim() ) ); + } else { + // overwrite "old" values by deleting them before adding the exact same combination + // this approach has limits, but it will work in 99% of cases. + + if ( conffile.contains("-overwrite") ) for(int i=0; i < reactions.size(); i++) { + Reaction r = (Reaction)reactions.get(i); + if ( r.positives.equals( level2[0].trim() ) && r.negatives.equals( "" ) ) { + reactions.remove( r ); + } + } + + reactions.add( new Reaction( level2[0].trim() , "" , level2[1].trim() ) ); + } + + } else if ( level1[0].trim().equals("command") ) { + + if ( level2.length == 4 ) { + commands.add( new Command( level2[0].trim() , level2[1].trim() , level2[2].trim(), level2[3].trim() ) ); + } else if ( level2.length == 3 ) { + commands.add( new Command( level2[0].trim() , level2[1].trim() , level2[2].trim(), "" ) ); + } else commands.add( new Command( level2[0].trim() , level2[1].trim() , "" , "") ); + + } else if ( level1[0].trim().equals("mailbox") ) { + + if ( level2.length == 8 ) { + + // log("mailbox.username="+ level2[1].trim() +" mailbox.secure = "+ level2[4].trim() ); + + mailboxes.add( + new MailboxData( mbxid++, level2[0].trim(), + level2[1].trim(), + level2[2].trim(), + level2[3].trim(), + Boolean.parseBoolean(level2[4].trim()),Integer.parseInt(level2[5].trim()), + Boolean.parseBoolean(level2[6].trim()),Integer.parseInt(level2[7].trim()) ) + ); + } else { + log("ERROR:syntaxerror:config:"+line); + } + + } else { + + if ( level2.length == 2 ) { + + config.put( level1[0].trim() , level2[0].trim() , level2[1].trim() ); + + } else config.put( level1[0].trim() , level2[0].trim() , "" ); + + } + } catch (Exception e) { + log("ERROR:syntaxerror:config:"+line); + log(e.getMessage()); + e.printStackTrace(); + return; + } + } + } + } + + if ( dos.fileExists( getHome()+"/.config/pva/timers.conf" ) ) { + timedata = dos.readFile( getHome()+"/.config/pva/timers.conf" ).split("\n"); + for(String data: timedata) { + if ( !data.trim().isEmpty() ) { + String[] x = data.split(":"); + timers.put(x[0],x[1]); + } + } + } else { + timedata = "".split(":"); // create empty array + } + + // check if we have metadata support is enabled + + metadata_enabled = config.get("conf","metadatabase"); + + if ( metadata_enabled.equals("true") ) { + + if ( dos.fileExists( getHome()+"/.cache/pva/cache.metadata" ) ) { + + StringHash unique = new StringHash(); + String[] cachedata = dos.readFile( getHome()+"/.cache/pva/cache.metadata" ).trim().split("\n"); + for(String line : cachedata ) { + String[] metacols = line.split( config.get("conf","splitter") ); + + // log("found "+ metacols[7] ); + + if ( metacols[7] != "null" && metacols[7] != "unknown" && metacols[7] != "Unknown" ) { + // add Genre to list + + if ( unique.get( metacols[7] ).equals("") ) { + unique.put( metacols[7] , "1"); + categories.add( metacols[7] ); +// log( "add "+ metacols[7] ); + } + } + } + } + + } + + // read in vcard cache + // without this cache it would take several minutes to import addressbooks + // from time to time you should refresh it, by just deleteing it + + String vcards = dos.readFile( getHome()+"/.cache/pva/vcards.cache" ); + if ( vcards.isEmpty() ) { + StringHash adb = config.get("addressbook"); + if ( adb != null ) { + say( texte.get( config.get("conf","lang_short"), "READPHONEBOOK") ); + + Enumeration en = adb.keys(); + while ( en.hasMoreElements() ) { + String key = (String) en.nextElement(); + String value = adb.get(key); + log( key ); + String[] url = key.split("/"); + String basedomain = url[0]+"//"+url[2]; + String[] html = dos.readPipe("curl "+ key +" --anyauth -u \""+ value +"\" 2>/dev/null").split("\n"); + int c = 1, gesamt=0; + for(String line : html ) + if ( line.contains("vcf\">/dev/null").split("\n"); + Contact vcf = new Contact(); + for(String vline : vcard ) { + vcf.parseInput( vline ); + } + contacts.addElement( vcf ); + c++; + } + + } + + } + } + + vcards = ""; + Enumeration een = contacts.elements(); + if ( een != null ) while ( een.hasMoreElements() ) { + Contact c = een.nextElement(); + vcards += c.exportVcard()+"XXXXXX---NEXT_ELEMENT---XXXXXX\n"; + } + dos.writeFile(getHome()+"/.cache/pva/vcards.cache", vcards); + } else { + // log("Loading cache..."); + String[] x = vcards.split("XXXXXX---NEXT_ELEMENT---XXXXXX\n"); + for(String card : x) { + Contact vcf = new Contact(); + vcf.importVcard( card ); + contacts.addElement( vcf ); + } + + } + + // for speed resons, we got often used content in variables. + keyword = config.get("conf","keyword"); + + // init AI + + + StringHash ai = config.get("ai"); + if ( ai != null ) { + HTTP.apihost = ai.get("host"); + HTTP.apiport = ai.get("port"); + HTTP.get("/api/tags"); + + String answere = HTTP.post("/api/generate","{\"model\": \""+ ai.get("model")+"\", \"prompt\": \"\\nGenerate a title following these rules:\\n - The title should be based on the prompt at the end\\n - Keep it in the same language as the prompt\\n - The title needs to be less than 30 characters\\n - Use only alphanumeric characters and spaces\\n - Just write the title, NOTHING ELSE\\n\\n```PROMPT\\nHallo\\n```\", \"stream\": false}"); + log("INIT AI: "+ answere ); + aimsgs.addMessage(new AIMessage("user", "User", "Hallo" )); + + } + + // here MAIN really starts + + PVA pva = new PVA(); + + log("start TimerTask"); + + tt = new TimerTask(pva); + tt.start(); + + log("start IMAPTask"); + + it = new IMAPTask(pva); + it.start(); + + log("start PluginLoader"); + + pls = new Plugins(pva); + + log("PVA:main:init audio"); + + if ( initPulseAudio() ) { + // enable input source for sure, in case we got restarted in mid blockade of the mic input + dos.readPipe("pactl set-source-output-mute "+ pa_outputid +" 0"); + } + + log("start server"); + + server = new Server( Integer.parseInt( config.get("network","port") ) , pva ); + server.startServing(); + + // Wait until be receive ctrl+c or the EXIT command is given + + pls.shutdown(); + + log("PVA:main:shutdown"); + + tt.interrupt(); + it.interrupt(); + Thread.currentThread().interrupt(); + + log("PVA:main:shutdown2"); + + // as this does not work all ... hardcore exit :D Some subthreads seem to block the JVM shutdown, but there is no hint which one it is. + + String pid = dos.readPipe("/usr/bin/pgrep -u "+ System.getenv("USER").trim() +" -f server.PVA"); + if ( !pid.trim().isEmpty() ) { + String[] lines = pid.trim().split("\n"); + for(String line : lines ) { + log("kill -9 "+ line); + dos.readPipe("kill -9 "+ line); + } + } else log("no pid to kill"); + + log("PVA:main:shutdown3"); // will never be visible in the logs ... is everything works + + } catch (Exception e) { + + e.printStackTrace(); + System.out.println(e); + + } + } + + public void handleInput(String extText) throws IOException,InterruptedException { + + reaction = false; + + // JSON Object is given by vosk, but the org.json package can't be shipped with distros, so we need to do it ourself, so .. don't wonder ;) + + // Format to parse "{text:"spoken text"}" + + // log("handleInput: text="+ extText); + + if ( extText.contains(":") ) { + if ( extText.split(":").length > 1 ) { + text = Tools.zwischen( extText.split(":")[1],"\"","\""); + if ( text == null ) text = ""; + } else text = ""; + } else { + text = extText; + } + + // REJECT ANY attempt to inject Code + // + // no regex check here, as someone could try to trick this with escapes + + if ( text.contains(";") || text.contains("|") || text.contains("'") || text.contains("\"") ) { + log("Exploit detected on input: "+ text); + return; + } + +// log("handleInput: text="+ text ); + + // RAW Copy of what has been spoken. This is needed in case a filter is applied, but we need some of the filtered words to make decisions. + + String text_raw = text; + + // Exit if there is nothing to process + + if ( text.trim().isEmpty() ) return; + + + if ( debug > 1 ) log("raw="+ text); + + if ( debug > 2 ) log("LANG="+ config.get("conf","lang") ); + + // generate words for numbers 1-99 + // use $HOME/.config/pva/conf.d/02-numbers-language.conf to overwrite the german defaults, or, systemwide, /etc/pva/99-numbers-language.conf + + String[] bloecke = config.get("conf","blocks").split(":"); + String[] ziffern = config.get("conf","numerics").split(":"); + String zahlen = config.get("conf","numbers"); + for(String zig : bloecke ) { + zahlen += zig+":"; + for(String zahl : ziffern ) + zahlen += zahl+ config.get("conf","numericsbindword") +zig+":"; + } + + za = zahlen.replaceAll(":$","").split(":"); + +// for( String x: za) log( x ); + +// log("TEXT:" + text); + + // now some error corrections for bugs in vosk or the way you speak to your pc ;) + + StringHash rep = config.get("replacements"); + if ( rep != null && rep.size() > 0 ) { + + text=text.trim(); + Enumeration en = rep.keys(); + while(en.hasMoreElements() ) { + String key = (String)en.nextElement(); + String value = rep.get(key); + + Matcher m = Pattern.compile("(?m)"+key ).matcher(text); + + if ( m.find() ) { + text = text.replaceAll( key, value ); + } + } + text=text.trim(); + } + + // some context less reactions + + Vector temp = new Vector(); + + for(int i=0; i < reactions.size(); i++) { + + Reaction r = (Reaction)reactions.get(i); + + if ( und( r.positives ) && ( r.negatives.isEmpty() || !und( r.negatives ) ) ) { + + temp.add( r ); + + } + } + + boolean nightprotection = false; + if ( config.get("conf","nightprotection").trim().toLowerCase().equals("on") ) { + try { + String[] times = config.get("conf","nighttime").split("-"); + if ( times.length == 2 ) { + String[] nightprotection_start = times[0].split(":"); + String[] nightprotection_end = times[1].split(":"); + + long sh = Long.parseLong( nightprotection_start[0] ); + long sm = Long.parseLong( nightprotection_start[1] ); + long eh = Long.parseLong( nightprotection_end[0] ); + long em = Long.parseLong( nightprotection_end[1] ); + Calendar cnow = Calendar.getInstance(); + + long h = cnow.get(Calendar.HOUR_OF_DAY); + long m = cnow.get(Calendar.MINUTE); + + if ( h >= sh && m >= sm && ( h < eh || ( h == eh && m <= em ) ) ) + nightprotection = true; + + } else log("sorry, you messed up the time intervall for nightprotection! "+ config.get("conf","nighttime")); + } catch (Exception e) { + log("Sorry, you messed up the time intervall for nightprotection! "+ e); + e.printStackTrace(); + } + } + + if ( temp.size() > 0 && !nightprotection ) { + Reaction r = null; + String lastr = dos.readFile(getHome()+"/.cache/pva/reaction.last"); + // lastr == "" means, it did not exist + do { + r = temp.get( (int)( Math.random() * temp.size() ) ); + } while ( r.answere.equals( lastr ) && temp.size()>1 ); + + if ( checkMediaPlayback() ) + say( r.answere.replaceAll("%KEYWORD", keyword ),true); + + dos.writeFile( getHome()+"/.cache/pva/reaction.last" , r.answere ); + } else if ( temp.size() > 0 && nightprotection ) log("Silentmode active - Reaction surpressed"); + + StringHash ai = config.get("ai"); + boolean aiportreachable = false; + String nt = dos.readPipe("env LANG=C netstat -lna"); + + if ( ! nt.isEmpty() ) + for(String a: nt.split("\n") ) + if ( a.matches( ".*:"+ ai.get("port") +".*LISTEN.*" ) ) aiportreachable = true; + + if ( !wort(keyword) ) { + + // we need a sentence detection against the noise + if ( ai != null ) { + + if ( ai.get("enable").equals("true") && aiportreachable ) { + // check a: no reaction happend + freetalk mode + more than 3 words are used + // OR + // check b: keyword mode is enabled and keyword is in textblock + + if ( ( temp.size() == 0 && ai.get("mode").equals("freetalk") && text.trim().split(" ").length > 3 ) || + ( ai.get("mode").equals("keyword") && wort( ai.get("keyword") ) ) ) { + + if ( ai.get("mode").equals("keyword") && wort( ai.get("keyword") ) ) + text = text.substring( text.indexOf(ai.get("keyword"))+ ai.get("keyword").length() ).trim(); + + if ( ( ( ai.get("mode").equals("freetalk") && checkMediaPlayback() ) || !ai.get("mode").equals("freetalk") ) && text.trim().length()>0 ) { + +// log("ai:send:" + text); + + HTTP.apihost = ai.get("host"); + HTTP.apiport = ai.get("port"); + + aimsgs.addMessage(new AIMessage("user", "User", text )); + +// log("messages = "+ aimsgs.toJSON() ); + + String answere = HTTP.post("/api/chat","{\"model\":\""+ ai.get("model")+"\",\"stream\": false,\"messages\":"+ aimsgs.toJSON() +"}"); + if ( answere != null ) { + + answere = parseJSON(answere,ai.get("model")).trim(); + log("we got back:" + answere); + + say( answere,true ); + reaction = true; + } + } + } + } else if ( ai.get("bin") == null ) log("no config for ai found"); + } else if ( debug > 2 ) log("no ai config"); + } + + StringHash cgpt = config.get("chatgpt"); + if ( !wort(keyword) ) { + + // we need a sentence detection against the noise + + if ( cgpt != null ) { + if ( cgpt.get("enable").equals("true") && cgpt.get("bin") != null ) { + // check a: no reaction happend + freetalk mode + more than 3 words are used + // OR + // check b: keyword mode is enabled and keyword is in textblock + + if ( ( temp.size() == 0 && cgpt.get("mode").equals("freetalk") && text.trim().split(" ").length > 3 ) || + ( cgpt.get("mode").equals("keyword") && wort( cgpt.get("keyword") ) ) ) { + + if ( cgpt.get("mode").equals("keyword") && wort( cgpt.get("keyword") ) ) + text = text.substring( text.indexOf(cgpt.get("keyword"))+ cgpt.get("keyword").length() ).trim(); + + if ( ( ( cgpt.get("mode").equals("freetalk") && checkMediaPlayback() ) || !cgpt.get("mode").equals("freetalk") ) && text.trim().length()>0 ) { + + log("chatgpt:send:" + text); + String answere = dos.readPipe( config.get("chatgpt","bin") +" \""+ text +"\"" ); + if ( answere != null ) { + answere = answere.trim(); + log("we got back:" + answere); + + say( answere,true ); + reaction = true; + } + } + } // else say( texte.get( config.get("conf","lang_short"), "CHATGPTNOTENOUGHWORDSTOPROCESS"), true ); + } else if ( cgpt.get("bin") == null ) log("no config for chatgpt found"); + } else if ( debug > 2 ) log("no chatgpt"); + } + + // now the part that interessts you most .. the context depending parser + + // before we start the static methods: + // wort("term") means exactly this term in the variable "text" + // oder("term|term2") means one of these terms + // und("term|term2") means all of these terms, but the order is irrelevant und("eins|zwei|drei") works on "drei zwei eins" same as on "sprecher nummer drei hatte um eins einen termin mit zwei leuten" + // in later versions of this app, we will have a regexp database that will do this work as far as possible + + if ( wort(keyword) ) { + + // remove everything BEFORE the keyword, because the keyword can be at any position in a recording + // also remove the keyword itself + + // ADVISE: remove any keyword that led to your reaction + // in the sentence "carola i want listen to queen" anything thats not part of the searchterm "queen" needs to go or your search is pointless :) + + text = text.substring( text.indexOf(keyword) + keyword.length() ); + + // It can be very helpfull to output the sentence vosk heared to see, whats going wrong. + + log(text); + + // parse commands from config + + text = text.trim(); + + Command cf = parseCommand( text ); + + log ( "found "+ cf.command +": "+text ); + + if ( cf.command.startsWith("EXEC:") ) { + + // Format: EXEC:[/path/to/]exec[argument1argument2...] + // Example: "EXEC:pulse.outx:xqmmpx:xhdmi" translates to "pulse.out qmmp hdmi" + + // extract cmd + + String[] ecmd = cf.command.split(":",2); + if ( ecmd.length > 1 ) { + // Execute cmd without reprocessing + + String term = ecmd[1]; + + // log( "EXEC:"+ term ); + + while ( term.indexOf("{") >= 0 ) + term = replaceVariables( term ); + + // replace Terms, IF a Vector cf.terms are present and filled + + if ( cf.terms.size() > 0 ) + for(int i=0;i 0 ) { + String newmode = ((String)cf.terms.get(0)).trim(); + + for(String a : aliases ) { + String[] opts = a.split("="); + if ( opts[0].trim().equals( newmode ) ) { + log( "chatgpt:swap: "+ config.get("chatgpt","mode") + " => "+ opts[1].trim() ) ; + config.put("chatgpt","mode", opts[1].trim() ); + reaction = true; + } + } + + } else { + say( texte.get( config.get("conf","lang_short"), "SYNTAXERROR") ); + } + } + + + + // The so called Star Trek part :) + if ( cf.command.equals("AUTHORIZE") ) { + + if ( wort( config.get("code","alpha")) ) { + String cmd = dos.readFile(getHome()+"/.cache/pva/cmd.last"); + if ( cmd.equals("exit") ) { + + say( texte.get( config.get("conf","lang_short"), "QUIT") ); + + // shutdown normally, kill python STT process + server.interrupt(); + + String[] e = dos.readPipe("pgrep -i -l -a python").split("\n"); + for(String a : e ) { + if ( a.contains("pva.py") ) { + String[] b = a.split(" "); + exec("kill "+ b[0] ); + } + } + } + if ( cmd.equals("autoexit") ) { + + // shutdown normally, kill python STT process + + server.interrupt(); + + String[] e = dos.readPipe("pgrep -i -l -a python").split("\n"); + for(String a : e ) { + if ( a.contains("pva.py") ) { + String[] b = a.split(" "); + exec("kill "+ b[0] ); + } + } + } + if ( cmd.equals("compile") ) { + say( texte.get( config.get("conf","lang_short"), "RECOMPILING") ); + System.out.println( dos.readPipe("compile.sh") ); + } + return; + } else if ( wort( config.get("code","beta")) ) { + String cmd = dos.readFile(getHome()+"/.cache/pva/cmd.last"); + if ( cmd.equals("unlockscreen") ) { + say( texte.get( config.get("conf","lang_short"), "UNLOCKSCREENRESPONSE") ); + if ( getDesktop().equals("cinnamon") ) { + dos.readPipe("/usr/bin/cinnamon-screensaver-command -e"); + } else if ( getDesktop().equals("gnome") ) { + dos.readPipe("/usr/bin/gnome-screensaver-command -e"); + } + } + return; + } else say( texte.get( config.get("conf","lang_short"), "FALSEAUTHCODE") ); + + } + + if ( cf.command.equals("SHOWTIMER") ) { + + String mytext = ""; + + for(String data: timedata) { + if ( !data.trim().isEmpty() ) { + String[] x = data.split(":"); + + text += texte.get( config.get("conf","lang_short"), "SHOWTIMERRESPONSE") + .replaceAll("", makeDate( Long.parseLong( x[0] ) ) ) + .replaceAll("", x[1])+"\n"; + } + } + + if ( mytext.isEmpty() ) { + say( config.get( config.get("conf","lang_short"), "SHOWTIMERRESPONSENOTHING") ); + } else { + log( mytext ); + say( mytext ); + } + + } + + // MAKETIMER + // Example: " erinnere mich um achtzehn uhr dreißig an linux am dienstag" + // Example: " reminde me at