diff --git a/iped-app/resources/localization/iped-parsers-messages.properties b/iped-app/resources/localization/iped-parsers-messages.properties index ee4e2615c4..c9836cfa6e 100644 --- a/iped-app/resources/localization/iped-parsers-messages.properties +++ b/iped-app/resources/localization/iped-parsers-messages.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=Found in Child Porn Alert Hash Database WhatsAppReport.Owner=Owner WhatsAppReport.Recovered=Recovered WhatsAppReport.QuoteNotFound=Quoted message not found +WhatsAppReport.QuoteStaus=Quoted Status +WhatsAppReport.QuotePrivacy=Message quoted privately through the group +WhatsAppReport.QuotePrivacyMessage=This quoted message was created in the group +WhatsAppReport.QuotePrivacyNotFound=Message quoted privately through the group not found +WhatsAppReport.QuoteCatalog=Quoted Catalog +WhatsAppReport.ContactedFindBusinesses=You contacted {0} from find businesses WhatsAppReport.Document=Document WhatsAppReport.Photo=Photo WhatsAppReport.Audio=Audio @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Edited on WhatsAppReport.UserJoinedWhatsApp=joined WhatsApp WhatsAppReport.PinnedMessage=pinned a message WhatsAppReport.AIThirdParty=This AI is from a third-party developer. Meta receives your AI chats to improve AI quality. +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=This group has over 256 members so now only admins can edit the groups settings. +WhatsAppReport.SecurityNotificationsNoLongerAvailable=Security code notifications are no longer available for this chat. VCardParser.FormattedName=Formatted Name VCardParser.Name=Name VCardParser.Nickname=Nickname diff --git a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties index 06d93740bf..75f3839d10 100644 --- a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties +++ b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=gefunden in KiPo Hash Database WhatsAppReport.Owner=Besitzer WhatsAppReport.Recovered=Recovered[TBT] WhatsAppReport.QuoteNotFound=Quoted message not found[TBT] +WhatsAppReport.QuoteStaus=Quoted Status[TBT] +WhatsAppReport.QuotePrivacy=Message quoted privately through the group[TBT] +WhatsAppReport.QuotePrivacyMessage=This quoted message was created in the group[TBT] +WhatsAppReport.QuotePrivacyNotFound=Message quoted privately through the group not found[TBT] +WhatsAppReport.QuoteCatalog=Quoted Catalog[TBT] +WhatsAppReport.ContactedFindBusinesses=You contacted {0} from find businesses[TBT] WhatsAppReport.Document=Document[TBT] WhatsAppReport.Photo=Photo[TBT] WhatsAppReport.Audio=Audio[TBT] @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Edited on[TBT] WhatsAppReport.UserJoinedWhatsApp=joined WhatsApp[TBT] WhatsAppReport.PinnedMessage=pinned a message[TBT] WhatsAppReport.AIThirdParty=This AI is from a third-party developer. Meta receives your AI chats to improve AI quality.[TBT] +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=Diese Gruppe hat mehr als 256 Mitglieder. Daher können jetzt nur noch Admins die Gruppeneinstellungen bearbeiten. +WhatsAppReport.SecurityNotificationsNoLongerAvailable=Benachrichtigungen zur Sicherheitsnummer sind für diesen Chat nicht länger verfügbar. VCardParser.FormattedName=Name formatiert VCardParser.Name=Name VCardParser.Nickname=Nickname diff --git a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties index 98359140cc..8f2e03964c 100644 --- a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties +++ b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=Encontrado en la base de datos Hash de Alerta d WhatsAppReport.Owner=Propietario WhatsAppReport.Recovered=Recovered[TBT] WhatsAppReport.QuoteNotFound=Quoted message not found[TBT] +WhatsAppReport.QuoteStaus=Quoted Status[TBT] +WhatsAppReport.QuotePrivacy=Message quoted privately through the group[TBT] +WhatsAppReport.QuotePrivacyMessage=This quoted message was created in the group[TBT] +WhatsAppReport.QuotePrivacyNotFound=Message quoted privately through the group not found[TBT] +WhatsAppReport.QuoteCatalog=Quoted Catalog[TBT] +WhatsAppReport.ContactedFindBusinesses=You contacted {0} from find businesses[TBT] WhatsAppReport.Document=Document[TBT] WhatsAppReport.Photo=Photo[TBT] WhatsAppReport.Audio=Audio[TBT] @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Edited on[TBT] WhatsAppReport.UserJoinedWhatsApp=joined WhatsApp[TBT] WhatsAppReport.PinnedMessage=pinned a message[TBT] WhatsAppReport.AIThirdParty=This AI is from a third-party developer. Meta receives your AI chats to improve AI quality.[TBT] +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=This group has over 256 members so now only admins can edit the groups settings.[TBT] +WhatsAppReport.SecurityNotificationsNoLongerAvailable=Security code notifications are no longer available for this chat.[TBT] VCardParser.FormattedName=Nombre con formato VCardParser.Name=Nombre VCardParser.Nickname=Sobrenombre diff --git a/iped-app/resources/localization/iped-parsers-messages_fr_FR.properties b/iped-app/resources/localization/iped-parsers-messages_fr_FR.properties index a1b81febd7..b4f6b74443 100644 --- a/iped-app/resources/localization/iped-parsers-messages_fr_FR.properties +++ b/iped-app/resources/localization/iped-parsers-messages_fr_FR.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=Contenu pédopornographique détecté via la ba WhatsAppReport.Owner=Propriétaire WhatsAppReport.Recovered=Récupéré WhatsAppReport.QuoteNotFound=Message cité est introuvable +WhatsAppReport.QuoteStaus=Quoted Status[TBT] +WhatsAppReport.QuotePrivacy=Message quoted privately through the group[TBT] +WhatsAppReport.QuotePrivacyMessage=This quoted message was created in the group[TBT] +WhatsAppReport.QuotePrivacyNotFound=Message quoted privately through the group not found[TBT] +WhatsAppReport.QuoteCatalog=Quoted Catalog[TBT] +WhatsAppReport.ContactedFindBusinesses=You contacted {0} from find businesses[TBT] WhatsAppReport.Document=Document WhatsAppReport.Photo=Photo WhatsAppReport.Audio=Audio @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Modifié en WhatsAppReport.UserJoinedWhatsApp=a rejoint WhatsApp WhatsAppReport.PinnedMessage=a epinglé un message WhatsAppReport.AIThirdParty=Cette IA provient d'un développeur tiers. Meta reçoit vos discussions IA pour améliorer la qualité de l'IA. +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=Comme ce groupe inclut plus de 256 membres, désormais, seulement les admins peuvent modifier les paramètres du groupe. +WhatsAppReport.SecurityNotificationsNoLongerAvailable=Les notifications relatives aux codes de sécurité ne sont plus disponibles pour cette discoussion. VCardParser.FormattedName=Nom formaté VCardParser.Name=Nom VCardParser.Nickname=Surnom diff --git a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties index abfee09355..c03a30d7e1 100644 --- a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties +++ b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=Trovato nel Database di materiale pedopornograf WhatsAppReport.Owner=Proprietario WhatsAppReport.Recovered=Recovered[TBT] WhatsAppReport.QuoteNotFound=Quoted message not found[TBT] +WhatsAppReport.QuoteStaus=Quoted Status[TBT] +WhatsAppReport.QuotePrivacy=Message quoted privately through the group[TBT] +WhatsAppReport.QuotePrivacyMessage=This quoted message was created in the group[TBT] +WhatsAppReport.QuotePrivacyNotFound=Message quoted privately through the group not found[TBT] +WhatsAppReport.QuoteCatalog=Quoted Catalog[TBT] +WhatsAppReport.ContactedFindBusinesses=You contacted {0} from find businesses[TBT] WhatsAppReport.Document=Document[TBT] WhatsAppReport.Photo=Photo[TBT] WhatsAppReport.Audio=Audio[TBT] @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Edited on[TBT] WhatsAppReport.UserJoinedWhatsApp=joined WhatsApp[TBT] WhatsAppReport.PinnedMessage=pinned a message[TBT] WhatsAppReport.AIThirdParty=This AI is from a third-party developer. Meta receives your AI chats to improve AI quality.[TBT] +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=Dato che questo gruppo ha più di 256 membri, solo gli amministratori potranno modificarne le impostazioni. +WhatsAppReport.SecurityNotificationsNoLongerAvailable=Le notifiche sul codice di sicurezza non sono più disponibili per questa chat. VCardParser.FormattedName=Nome formattato VCardParser.Name=Nome VCardParser.Nickname=Nickname diff --git a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties index 81c372bb6e..fae85613f2 100644 --- a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties +++ b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties @@ -276,6 +276,12 @@ WhatsAppReport.FoundInPedoHashDB=Encontrado em base de hashes de pornografia inf WhatsAppReport.Owner=Proprietário WhatsAppReport.Recovered=Recuperado WhatsAppReport.QuoteNotFound=Mensagem citada não localizada +WhatsAppReport.QuoteStaus=Status citado +WhatsAppReport.QuotePrivacy=Mensagem citada em particular através de grupo +WhatsAppReport.QuotePrivacyMessage=Esta mensagem citada foi criada no grupo +WhatsAppReport.QuotePrivacyNotFound=Mensagem citada em particular através de grupo não encontrada +WhatsAppReport.QuoteCatalog=Catálogo citado +WhatsAppReport.ContactedFindBusinesses=Você entrou em contato com a empresa {0} usando o recurso de encontrar empresas WhatsAppReport.Document=Documento WhatsAppReport.Photo=Foto WhatsAppReport.Audio=Áudio @@ -325,6 +331,8 @@ WhatsAppReport.EditedOn=Editada em WhatsAppReport.UserJoinedWhatsApp=entrou no WhatsApp WhatsAppReport.PinnedMessage=fixou uma mensagem WhatsAppReport.AIThirdParty=Esta IA pertence a um desenvolvedor terceirizado. A Meta recebe suas conversas com IA para melhorar a qualidade desse recurso. +WhatsAppReport.Over256MembersOnlyAdminsCanEdit=Agora somente admins podem editar as configurações porque o grupo tem mais de 256 membros. +WhatsAppReport.SecurityNotificationsNoLongerAvailable=As notificações sobre o código de segurança não estão mais disponíveis para esta conversa. VCardParser.FormattedName=Nome Formatado VCardParser.Name=Nome VCardParser.Nickname=Apelido diff --git a/iped-app/resources/scripts/tasks/WhisperProcess.py b/iped-app/resources/scripts/tasks/WhisperProcess.py index a53203bc47..e31c9a1854 100644 --- a/iped-app/resources/scripts/tasks/WhisperProcess.py +++ b/iped-app/resources/scripts/tasks/WhisperProcess.py @@ -1,4 +1,4 @@ -import sys +import sys import numpy stdout = sys.stdout sys.stdout = sys.stderr @@ -10,7 +10,6 @@ ping = 'ping' def main(): - modelName = sys.argv[1] deviceNum = int(sys.argv[2]) threads = int(sys.argv[3]) @@ -74,38 +73,44 @@ def main(): if line == ping: print(ping, file=stdout, flush=True) continue - - transcription = '' + + files = line.split(",") + transcription = [] logprobs = [] + for file in files: + transcription.append("") + logprobs.append([]) try: if whisperx_found: - audio = whisperx.load_audio(line) - result = model.transcribe(audio, batch_size=batch_size, language=language) + result = model.transcribe(files, batch_size=batch_size, language=language,wav=True) for segment in result['segments']: - transcription += segment['text'] + idx = segment["audio"] + transcription[idx] += segment['text'] if 'avg_logprob' in segment: - logprobs.append(segment['avg_logprob']) + logprobs[idx].append(segment['avg_logprob']) else: - segments, info = model.transcribe(audio=line, language=language, beam_size=5, vad_filter=True) - for segment in segments: - transcription += segment.text - logprobs.append(segment.avg_logprob) + for idx in range(len(files)): + segments, info = model.transcribe(audio=files[idx], language=language, beam_size=5, vad_filter=True) + for segment in segments: + transcription[idx] += segment.text + logprobs[idx].append(segment.avg_logprob) except Exception as e: msg = repr(e).replace('\n', ' ').replace('\r', ' ') print(msg, file=stdout, flush=True) continue - text = transcription.replace('\n', ' ').replace('\r', ' ') - - if len(logprobs) == 0: - finalScore = 0 - else: - finalScore = numpy.mean(numpy.exp(logprobs)) - print(finished, file=stdout, flush=True) - print(str(finalScore), file=stdout, flush=True) - print(text, file=stdout, flush=True) + + for idx in range(len(files)): + text = transcription[idx].replace('\n', ' ').replace('\r', ' ') + + if len(logprobs[idx]) == 0: + finalScore = 0 + else: + finalScore = numpy.mean(numpy.exp(logprobs[idx])) + print(str(finalScore), file=stdout, flush=True) + print(text, file=stdout, flush=True) return diff --git a/iped-engine/src/main/java/iped/engine/task/transcript/AbstractTranscriptTask.java b/iped-engine/src/main/java/iped/engine/task/transcript/AbstractTranscriptTask.java index 8c66a3c569..bbd2637ea7 100644 --- a/iped-engine/src/main/java/iped/engine/task/transcript/AbstractTranscriptTask.java +++ b/iped-engine/src/main/java/iped/engine/task/transcript/AbstractTranscriptTask.java @@ -70,7 +70,7 @@ public abstract class AbstractTranscriptTask extends AbstractTask { private static final int MAX_WAV_SIZE = 16000 * 2 * MAX_WAV_TIME; protected AudioTranscriptConfig transcriptConfig; - + // Variables to store some statistics private static final AtomicLong wavTime = new AtomicLong(); private static final AtomicLong transcriptionTime = new AtomicLong(); @@ -91,8 +91,7 @@ public boolean isEnabled() { protected boolean isToProcess(IItem evidence) { - if (evidence.getLength() == null || evidence.getLength() == 0 || !evidence.isToAddToCase() - || evidence.getMetadata().get(ExtraProperties.TRANSCRIPT_ATTR) != null) { + if (evidence.getLength() == null || evidence.getLength() == 0 || !evidence.isToAddToCase() || evidence.getMetadata().get(ExtraProperties.TRANSCRIPT_ATTR) != null) { return false; } if (transcriptConfig.getSkipKnownFiles() && evidence.getExtraAttribute(HashDBLookupTask.STATUS_ATTRIBUTE) != null) { @@ -192,8 +191,7 @@ public void init(ConfigurationManager configurationManager) throws Exception { } - public static TextAndScore transcribeWavBreaking(File tmpFile, String itemPath, - Function transcribeWavPart) throws Exception { + public static TextAndScore transcribeWavBreaking(File tmpFile, String itemPath, Function transcribeWavPart) throws Exception { if (tmpFile.length() <= MAX_WAV_SIZE) { return transcribeWavPart.apply(tmpFile); } else { @@ -218,6 +216,10 @@ public static TextAndScore transcribeWavBreaking(File tmpFile, String itemPath, } protected static Collection getAudioSplits(File inFile, String itemPath) { + return getAudioSplits(inFile, itemPath, MAX_WAV_TIME); + } + + protected static Collection getAudioSplits(File inFile, String itemPath, int max_wave_time) { List splitFiles = new ArrayList(); AudioInputStream aIn = null; AudioInputStream aOut = null; @@ -226,7 +228,7 @@ protected static Collection getAudioSplits(File inFile, String itemPath) { outFile.delete(); aIn = AudioSystem.getAudioInputStream(inFile); int bytesPerFrame = aIn.getFormat().getFrameSize(); - int framesPerPart = Math.round(aIn.getFormat().getFrameRate() * MAX_WAV_TIME); + int framesPerPart = Math.round(aIn.getFormat().getFrameRate() * max_wave_time); byte[] partBytes = new byte[framesPerPart * bytesPerFrame]; int numBytesRead = 0; int seq = 0; @@ -312,7 +314,7 @@ public void finish() throws Exception { conn.close(); conn = null; } - + long totWavConversions = wavSuccess.longValue() + wavFail.longValue(); if (totWavConversions != 0) { LOGGER.info("Total conversions to WAV: " + totWavConversions); @@ -336,8 +338,7 @@ public void finish() throws Exception { } } - protected File getTempFileToTranscript(IItem evidence, TemporaryResources tmp) - throws IOException, InterruptedException { + protected File getTempFileToTranscript(IItem evidence, TemporaryResources tmp) throws IOException, InterruptedException { long t = System.currentTimeMillis(); File tempWav = null; try { @@ -369,8 +370,7 @@ protected void process(IItem evidence) throws Exception { return; } - if (evidence.getMetadata().get(ExtraProperties.TRANSCRIPT_ATTR) != null - && evidence.getMetadata().get(ExtraProperties.CONFIDENCE_ATTR) != null) + if (evidence.getMetadata().get(ExtraProperties.TRANSCRIPT_ATTR) != null && evidence.getMetadata().get(ExtraProperties.CONFIDENCE_ATTR) != null) return; TextAndScore prevResult = getTextFromDb(evidence.getHash()); diff --git a/iped-engine/src/main/java/iped/engine/task/transcript/RemoteTranscriptionService.java b/iped-engine/src/main/java/iped/engine/task/transcript/RemoteTranscriptionService.java index 39f974ddf1..f67a42fef5 100644 --- a/iped-engine/src/main/java/iped/engine/task/transcript/RemoteTranscriptionService.java +++ b/iped-engine/src/main/java/iped/engine/task/transcript/RemoteTranscriptionService.java @@ -16,6 +16,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -36,27 +38,31 @@ import iped.utils.IOUtil; public class RemoteTranscriptionService { - + // 30 minutos + private static final int MAX_WAV_TIME = 30 * 60; + private static final int MAX_WAV_SIZE = 16000 * 2 * MAX_WAV_TIME; + static enum MESSAGES { - ACCEPTED, - AUDIO_SIZE, - BUSY, - DISCOVER, - DONE, - ERROR, - REGISTER, - STATS, - WARN, VERSION_1_1, - VERSION_1_2, - VERSION_1_0, - PING + ACCEPTED, AUDIO_SIZE, BUSY, DISCOVER, DONE, ERROR, REGISTER, STATS, WARN, VERSION_1_1, VERSION_1_2, VERSION_1_0, PING + } + + static class TranscribeRequest { + File wavAudio; + TextAndScore result = null; + Exception error = null; + + public TranscribeRequest(File wavAudio) { + this.wavAudio = wavAudio; + } } - + static class OpenConnectons { Socket conn; BufferedInputStream bis; PrintWriter writer; Thread t; + File wavAudio; + TextAndScore result = null; public OpenConnectons(Socket conn, BufferedInputStream bis, PrintWriter writer, Thread t) { this.conn = conn; @@ -93,22 +99,21 @@ public void sendBeacon() { */ private static Semaphore wavConvSemaphore; + private static int BATCH_SIZE = 1; + private static final AtomicLong audiosTranscripted = new AtomicLong(); private static final AtomicLong audiosDuration = new AtomicLong(); private static final AtomicLong conversionTime = new AtomicLong(); private static final AtomicLong transcriptionTime = new AtomicLong(); private static final AtomicLong requestsReceived = new AtomicLong(); private static final AtomicLong requestsAccepted = new AtomicLong(); - private static List beaconQueq = new LinkedList<>(); + private static final List beaconQueq = new LinkedList<>(); + private static final Deque toTranscribe = new LinkedList<>(); private static Logger logger; private static void printHelpAndExit() { - System.out.println( - "Params: IP:Port [LocalPort]\n" - + "IP:Port IP and port of the naming node.\n" - + "LocalPort [optional] local port to listen for connections.\n" - + " If not provided, a random port will be used."); + System.out.println("Params: IP:Port [LocalPort]\n" + "IP:Port IP and port of the naming node.\n" + "LocalPort [optional] local port to listen for connections.\n" + " If not provided, a random port will be used."); System.exit(1); } @@ -149,7 +154,7 @@ public static void main(String[] args) throws Exception { AbstractTranscriptTask task = (AbstractTranscriptTask) Class.forName(audioConfig.getClassName()).getDeclaredConstructor().newInstance(); audioConfig.setEnabled(true); task.init(cm); - + BATCH_SIZE = audioConfig.getBatchSize(); int numConcurrentTranscriptions = Wav2Vec2TranscriptTask.getNumConcurrentTranscriptions(); int numLogicalCores = Runtime.getRuntime().availableProcessors(); @@ -175,6 +180,9 @@ public static void main(String[] args) throws Exception { startSendStatsThread(discoveryIp, discoveryPort, localPort, numConcurrentTranscriptions, numLogicalCores); startBeaconThread(); + for (int i = 0; i < numConcurrentTranscriptions; i++) { + startTrancribeThreads(task); + } waitRequests(server, task, discoveryIp); @@ -195,7 +203,7 @@ public void run() { Thread.sleep(60000); logger.info("Send beacons to {} clients", beaconQueq.size()); synchronized (beaconQueq) { - for( var cliente:beaconQueq) { + for (var cliente : beaconQueq) { cliente.sendBeacon(); } } @@ -208,14 +216,66 @@ public void run() { }); } + private static void transcribeAudios(AbstractTranscriptTask task) throws Exception { + ArrayList transcribeRequests = new ArrayList<>(); + ArrayList files = new ArrayList(); + + if (executor.isShutdown()) { + throw new Exception("Shutting down service instance..."); + } + + synchronized (toTranscribe) { + if (toTranscribe.size() == 0) + return; + while (toTranscribe.size() > 0 && transcribeRequests.size() < BATCH_SIZE) { + TranscribeRequest req = toTranscribe.poll(); + transcribeRequests.add(req); + files.add(req.wavAudio); + } + } + logger.info("inicio da transcricao de " + files.size() + " audios"); + + long t2 = System.currentTimeMillis(); + + boolean batchTrancribe = (task instanceof WhisperTranscriptTask); + if (batchTrancribe) { + try { + List results = ((WhisperTranscriptTask) task).transcribeAudios(files); + for (int i = 0; i < results.size(); i++) { + transcribeRequests.get(i).result = results.get(i); + } + } catch (Exception e) {// case fail, try each audio individually + batchTrancribe = false; + logger.error("Error while doing batch transcribe " + e.toString()); + } + } + if (!batchTrancribe) {// try each audio individually + for (int i = 0; i < files.size(); i++) { + try { + transcribeRequests.get(i).result = task.transcribeAudio(files.get(i)); + } catch (Exception e2) { + transcribeRequests.get(i).result = null; + transcribeRequests.get(i).error = e2; + logger.error("Error while transcribing"); + } + } + } + + for (int i = 0; i < transcribeRequests.size(); i++) { + synchronized (transcribeRequests.get(i)) { + transcribeRequests.get(i).notifyAll(); + } + } + long t3 = System.currentTimeMillis(); + transcriptionTime.addAndGet(t3 - t2); + } private static void registerThis(String discoveryIp, int discoveryPort, int localPort, int concurrentJobs, int concurrentWavConvs) throws Exception { try (Socket client = new Socket(discoveryIp, discoveryPort); InputStream is = client.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - PrintWriter writer = new PrintWriter( - new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) { + PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) { client.setSoTimeout(10000); writer.println(MESSAGES.REGISTER); @@ -289,14 +349,14 @@ public void run() { boolean error = false; OpenConnectons opc = null; String protocol = MESSAGES.VERSION_1_0.toString(); + ArrayList reqs = null; + try { client.setSoTimeout(CLIENT_TIMEOUT_MILLIS); bis = new BufferedInputStream(client.getInputStream()); - writer = new PrintWriter( - new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true); + writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true); - String clientName = "Client " + client.getInetAddress().getHostAddress() + ":" - + client.getPort(); + String clientName = "Client " + client.getInetAddress().getHostAddress() + ":" + client.getPort(); String prefix = clientName + " - "; writer.println(MESSAGES.ACCEPTED); @@ -312,29 +372,21 @@ public void run() { logger.info(prefix + "Accepted connection."); - int min = Math.min(MESSAGES.AUDIO_SIZE.toString().length(), - MESSAGES.VERSION_1_1.toString().length()); - - bis.mark(min + 1); - byte[] bytes = bis.readNBytes(min); - String cmd = new String(bytes); - if (!MESSAGES.AUDIO_SIZE.toString().startsWith(cmd)) { - bis.reset(); - bytes = bis.readNBytes(MESSAGES.VERSION_1_1.toString().length()); - protocol = new String(bytes); - bis.mark(min + 1); - synchronized (beaconQueq) { - opc = new OpenConnectons(client, bis, writer, this); - beaconQueq.add(opc); - } - + byte[] bytes = bis.readNBytes(MESSAGES.VERSION_1_2.toString().length()); + protocol = new String(bytes); + synchronized (beaconQueq) { + opc = new OpenConnectons(client, bis, writer, this); + beaconQueq.add(opc); } + logger.info("Protocol Version {}", protocol); + if (protocol.compareTo(MESSAGES.VERSION_1_2.toString()) < 0) { + throw new Exception("Procol version " + protocol + " not supported"); + } // read the audio_size message - bis.reset(); bytes = bis.readNBytes(MESSAGES.AUDIO_SIZE.toString().length()); - cmd = new String(bytes); + String cmd = new String(bytes); if (!MESSAGES.AUDIO_SIZE.toString().equals(cmd)) { error = true; @@ -343,11 +395,8 @@ public void run() { DataInputStream dis = new DataInputStream(bis); long size; - if (protocol.compareTo(MESSAGES.VERSION_1_2.toString()) >= 0) { - size = dis.readLong(); - } else { - size = dis.readInt(); - } + size = dis.readLong(); + if (size < 0) { error = true; try { @@ -408,17 +457,57 @@ public void run() { } long durationMillis = 1000 * wavFile.length() / (16000 * 2); - TextAndScore result; - long t2, t3; + TextAndScore result = new TextAndScore(); + result.text = ""; + result.score = 0; try { - transcriptSemaphore.acquire(); - if (executor.isShutdown()) { - error = true; - throw new Exception("Shutting down service instance..."); + reqs = new ArrayList(); + TranscribeRequest last = null; + if (wavFile.length() <= MAX_WAV_SIZE) { + TranscribeRequest req = new TranscribeRequest(wavFile); + reqs.add(req); + + } else { + + for (File wavPart : AbstractTranscriptTask.getAudioSplits(wavFile, wavFile.getPath(), MAX_WAV_TIME)) { + TranscribeRequest req = new TranscribeRequest(wavPart); + reqs.add(req); + } + logger.info(prefix + "Audio breaked into {} parts", reqs.size()); + wavFile.delete(); + + } + wavFile = null; + + // dispatch all parts to be executed + for (TranscribeRequest req : reqs) { + synchronized (toTranscribe) { + toTranscribe.add(req); + } + last = req; + } + + // wait until the last wav part is transcribed + synchronized (last) { + last.wait(); + } + + for (TranscribeRequest req : reqs) { + TextAndScore partResult = req.result; + if (partResult == null) { + error = false; + throw new Exception("Error processing the audio", req.error); + } + + if (result.score > 0) + result.text += " "; + result.text += partResult.text; + result.score += partResult.score; + result = req.result; + } - t2 = System.currentTimeMillis(); - result = task.transcribeAudio(wavFile); - t3 = System.currentTimeMillis(); + result.score /= reqs.size(); + } catch (ProcessCrashedException e) { // retry audio error = true; @@ -429,14 +518,12 @@ public void run() { executor.shutdown(); server.close(); throw e; - } finally { - transcriptSemaphore.release(); } audiosTranscripted.incrementAndGet(); audiosDuration.addAndGet(durationMillis); conversionTime.addAndGet(t1 - t0); - transcriptionTime.addAndGet(t3 - t2); + logger.info(prefix + "Transcritpion done."); // removes from the beacon queue to prevent beacons in the middle of the @@ -453,17 +540,8 @@ public void run() { String errorMsg = "Exception while transcribing"; logger.warn(errorMsg, e); if (writer != null) { - if (e.getMessage() != null && e.getMessage().startsWith("Invalid file size:") - && protocol.compareTo(MESSAGES.VERSION_1_2.toString()) < 0) { - writer.println("0"); - writer.println( - "Audios longer than 2GB are not supported by old clients, please update your client version!"); - writer.println(MESSAGES.DONE); - } else { - writer.println(error ? MESSAGES.ERROR : MESSAGES.WARN); - writer.println( - errorMsg + ": " + e.toString().replace('\n', ' ').replace('\r', ' ')); - } + writer.println(error ? MESSAGES.ERROR : MESSAGES.WARN); + writer.println(errorMsg + ": " + e.toString().replace('\n', ' ').replace('\r', ' ')); writer.flush(); } } finally { @@ -477,6 +555,13 @@ public void run() { if (wavFile != null) { wavFile.delete(); } + if (reqs != null) { + for (TranscribeRequest req : reqs) { + if (req.wavAudio != null && req.wavAudio != wavFile) { + req.wavAudio.delete(); + } + } + } removeFrombeaconQueq(opc); } } @@ -509,4 +594,37 @@ public void run() { }); } + private static void startTrancribeThreads(AbstractTranscriptTask task) { + executor.execute(new Runnable() { + @Override + public void run() { + while (true) { + Boolean empty = true; + synchronized (toTranscribe) { + empty = toTranscribe.isEmpty(); + } + if (empty) { + try { + Thread.sleep(100); + + } catch (Exception e) { + // TODO: handle exception + } + continue; + } + try { + transcriptSemaphore.acquire(); + transcribeAudios(task); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + transcriptSemaphore.release(); + } + + } + } + }); + } + } diff --git a/iped-engine/src/main/java/iped/engine/task/transcript/Wav2Vec2TranscriptTask.java b/iped-engine/src/main/java/iped/engine/task/transcript/Wav2Vec2TranscriptTask.java index 84a92dca8a..803fee7b12 100644 --- a/iped-engine/src/main/java/iped/engine/task/transcript/Wav2Vec2TranscriptTask.java +++ b/iped-engine/src/main/java/iped/engine/task/transcript/Wav2Vec2TranscriptTask.java @@ -27,18 +27,18 @@ public class Wav2Vec2TranscriptTask extends AbstractTranscriptTask { private static Logger logger = LogManager.getLogger(Wav2Vec2TranscriptTask.class); private static final String SCRIPT_PATH = "/scripts/tasks/Wav2Vec2Process.py"; - private static final String TRANSCRIPTION_FINISHED = "transcription_finished"; + protected static final String TRANSCRIPTION_FINISHED = "transcription_finished"; private static final String MODEL_LOADED = "wav2vec2_model_loaded"; private static final String HUGGINGSOUND_LOADED = "huggingsound_loaded"; private static final String TERMINATE = "terminate_process"; private static final String PING = "ping"; - private static final int MAX_TRANSCRIPTIONS = 100000; - private static final byte[] NEW_LINE = "\n".getBytes(); + protected static final int MAX_TRANSCRIPTIONS = 100000; + protected static final byte[] NEW_LINE = "\n".getBytes(); protected static volatile Integer numProcesses; - private static LinkedBlockingDeque deque = new LinkedBlockingDeque<>(); + protected static LinkedBlockingDeque deque = new LinkedBlockingDeque<>(); protected static volatile Level logLevel = Level.forName("MSG", 250); @@ -199,7 +199,7 @@ public void finish() throws Exception { deque.clear(); } - private void terminateServer(Server server) throws InterruptedException { + protected void terminateServer(Server server) throws InterruptedException { Process process = server.process; try { process.getOutputStream().write(TERMINATE.getBytes(Charsets.UTF8_CHARSET)); @@ -216,7 +216,7 @@ private void terminateServer(Server server) throws InterruptedException { } } - private boolean ping(Server server) { + protected boolean ping(Server server) { try { server.process.getOutputStream().write(PING.getBytes(Charsets.UTF8_CHARSET)); server.process.getOutputStream().write(NEW_LINE); diff --git a/iped-engine/src/main/java/iped/engine/task/transcript/WhisperTranscriptTask.java b/iped-engine/src/main/java/iped/engine/task/transcript/WhisperTranscriptTask.java index 697273efb2..aa519387d8 100644 --- a/iped-engine/src/main/java/iped/engine/task/transcript/WhisperTranscriptTask.java +++ b/iped-engine/src/main/java/iped/engine/task/transcript/WhisperTranscriptTask.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -17,6 +18,8 @@ import iped.engine.config.AudioTranscriptConfig; import iped.engine.config.Configuration; import iped.engine.config.ConfigurationManager; +import iped.engine.task.transcript.AbstractTranscriptTask.TextAndScore; +import iped.engine.task.transcript.Wav2Vec2TranscriptTask.Server; import iped.exception.IPEDException; public class WhisperTranscriptTask extends Wav2Vec2TranscriptTask { @@ -128,14 +131,62 @@ protected TextAndScore transcribeAudio(File tmpFile) throws Exception { return transcribeWavPart(tmpFile); } + protected List transcribeAudios(ArrayList tmpFiles) throws Exception { + + ArrayList textAndScores = new ArrayList<>(); + for (int i = 0; i < tmpFiles.size(); i++) { + textAndScores.add(null); + } + + Server server = deque.take(); + try { + if (!ping(server) || server.transcriptionsDone >= MAX_TRANSCRIPTIONS) { + terminateServer(server); + server = startServer(server.device); + } + + StringBuilder filePaths = new StringBuilder(); + for (int i = 0; i < tmpFiles.size(); i++) { + if (i > 0) { + filePaths.append(","); + } + filePaths.append(tmpFiles.get(i).getAbsolutePath().replace('\\', '/')); + + } + server.process.getOutputStream().write(filePaths.toString().getBytes("UTF-8")); + server.process.getOutputStream().write(NEW_LINE); + server.process.getOutputStream().flush(); + + String line; + while (!TRANSCRIPTION_FINISHED.equals(line = server.reader.readLine())) { + if (line == null) { + throw new ProcessCrashedException(); + } else { + throw new RuntimeException("Transcription failed, returned: " + line); + } + } + for (int i = 0; i < tmpFiles.size(); i++) { + Double score = Double.valueOf(server.reader.readLine()); + String text = server.reader.readLine(); + + TextAndScore textAndScore = new TextAndScore(); + textAndScore.text = text; + textAndScore.score = score; + textAndScores.set(i, textAndScore); + server.transcriptionsDone++; + } + + } finally { + deque.add(server); + } + + return textAndScores; + } + @Override protected void logInputStream(InputStream is) { - List ignoreMsgs = Arrays.asList( - "With dispatcher enabled, this function is no-op. You can remove the function call.", - "torchvision is not available - cannot save figures", - "Lightning automatically upgraded your loaded checkpoint from", - "Model was trained with pyannote.audio 0.0.1, yours is", - "Model was trained with torch 1.10.0+cu102, yours is"); + List ignoreMsgs = Arrays.asList("With dispatcher enabled, this function is no-op. You can remove the function call.", "torchvision is not available - cannot save figures", + "Lightning automatically upgraded your loaded checkpoint from", "Model was trained with pyannote.audio 0.0.1, yours is", "Model was trained with torch 1.10.0+cu102, yours is"); Thread t = new Thread() { public void run() { byte[] buf = new byte[1024]; diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/Messages.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/Messages.java index a3fc6e6ca7..c3ad3c63ba 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/Messages.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/Messages.java @@ -3,6 +3,7 @@ import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.text.MessageFormat; public class Messages { @@ -13,7 +14,7 @@ public class Messages { private Messages() { } - public static String getString(String key) { + public static String get(String key) { if (RESOURCE_BUNDLE == null) { String str = System.getProperty(iped.localization.Messages.LOCALE_SYS_PROP); // $NON-NLS-1$ Locale locale = str != null ? Locale.forLanguageTag(str) : Locale.getDefault(); @@ -27,4 +28,11 @@ public static String getString(String key) { throw e; } } + + public static String getString(String key, Object... args) { + String value = get(key); + if (args != null) + value = MessageFormat.format(value, args); + return value; + } } diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroid.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroid.java index 5f8519d2ec..7d8cb94984 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroid.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroid.java @@ -61,6 +61,7 @@ import iped.parsers.sqlite.SQLiteUndeleteTable; import iped.parsers.sqlite.SQLiteUndeleteTableResultSetAdapter; import iped.parsers.whatsapp.Message.MessageStatus; +import iped.parsers.whatsapp.Message.MessageQuotedType; /** * @@ -436,9 +437,10 @@ private List extractMessages(Connection conn, WAContact remote, boolean if (m != null){// Has quote Message original = messagesMapUuid.get(mq.getUuid());//Try to find orginal message in messages if (original != null){// has found original message reference, more complete + original.setMessageQuotedType(MessageQuotedType.QUOTE_FOUND); m.setMessageQuote(original); }else{// not found original message reference, get info from message_quotes table, less complete - mq.setDeleted(true); + mq.setMessageQuotedType(MessageQuotedType.QUOTE_NOT_FOUND); mq.setId(fakeIds--); m.setMessageQuote(mq); } diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroidNew.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroidNew.java index c7bd1ba379..a37f2609c6 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroidNew.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorAndroidNew.java @@ -29,6 +29,7 @@ import static iped.parsers.whatsapp.Message.MessageType.EPHEMERAL_SAVE; import static iped.parsers.whatsapp.Message.MessageType.GIF_MESSAGE; import static iped.parsers.whatsapp.Message.MessageType.GROUP_ADDED_TO_COMMUNITY; +import static iped.parsers.whatsapp.Message.MessageType.CONTACTED_FIND_BUSINESSES; import static iped.parsers.whatsapp.Message.MessageType.GROUP_CHANGED_ALL_MEMBERS_CAN_EDIT; import static iped.parsers.whatsapp.Message.MessageType.GROUP_CHANGED_ALL_MEMBERS_CAN_SEND; import static iped.parsers.whatsapp.Message.MessageType.GROUP_CHANGED_ONLY_ADMINS_CAN_ADD; @@ -88,6 +89,8 @@ import static iped.parsers.whatsapp.Message.MessageType.WAITING_MESSAGE; import static iped.parsers.whatsapp.Message.MessageType.YOU_ADMIN; import static iped.parsers.whatsapp.Message.MessageType.YOU_NOT_ADMIN; +import static iped.parsers.whatsapp.Message.MessageType.OVER_256_MEMBERS_ONLY_ADMINS_CAN_EDIT; +import static iped.parsers.whatsapp.Message.MessageType.SECURITY_NOTIFICATIONS_NO_LONGER_AVAILABLE; import java.io.File; import java.sql.Connection; @@ -106,6 +109,7 @@ import iped.parsers.sqlite.SQLite3DBParser; import iped.parsers.whatsapp.Message.MessageStatus; import iped.parsers.whatsapp.Message.MessageType; +import iped.parsers.whatsapp.Message.MessageQuotedType; /** * @@ -346,6 +350,17 @@ private void extractProductInfo(Connection conn, Message m) throws SQLException } } + private void extractQuotedProductInfo(Connection conn, Message m) throws SQLException { + try (PreparedStatement stmt = conn.prepareStatement(SELECT_QUOTED_PRODUCT)) { + stmt.setLong(1, m.getId()); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + m.setProduct(new MessageProduct(rs.getString("title"), rs.getString("seller"), rs.getString("currency"), + rs.getInt("amount"), rs.getString("description"))); + } + } + } + private void extractEphemeralDuration(Connection conn, Message m) throws SQLException { try (PreparedStatement stmt = conn.prepareStatement(SELECT_EPHEMERAL_SETTING)) { stmt.setLong(1, m.getId()); @@ -415,7 +430,7 @@ private void extractMessages(Connection conn, Map idToChat) throws S int actionType = rs.getInt("actionType"); m.setMessageType(decodeMessageType(type, status, edit_version, caption, actionType, - rs.getInt("bizStateId"), m.getMediaMime())); + rs.getInt("bizStateId"), rs.getInt("privacyType"), m.getMediaMime())); if (m.getMessageType() == EPHEMERAL_SETTINGS_NOT_APPLIED) { // Ignore this type of message, as it does nothing and it is not visible in the application itself. @@ -538,18 +553,70 @@ private void extractMessages(Connection conn, Map idToChat) throws S Message m = messagesMap.get(mq.getId()); if (m != null) { // Has quote - // Try to find original message in messages Message original = messagesMapUuid.get(mq.getUuid()); if (original != null) { - // has found original message reference, more complete + // Has found original message reference + original.setMessageQuotedType(MessageQuotedType.QUOTE_FOUND); m.setMessageQuote(original); - } else { - // not found original message reference, get info from message_quotes table, - // less complete - mq.setDeleted(true); + } else if (mq.getProduct() != null) { + // Quoted Catalog + mq.setMessageQuotedType(MessageQuotedType.QUOTE_CATALOG); mq.setId(fakeIds--); m.setMessageQuote(mq); + } else { + // Original message reference not found + if (chatId == mq.getQuoteChatId()) { + // Message in same chat + long editId = mq.getEditId(); + if (messagesMap.get(editId) != null) { + // Quoted was edited + mq.setMessageQuotedType(MessageQuotedType.QUOTE_FOUND); + mq.setId(editId); + } else { + // Quoted message cannot be found again ( maybe deleted or not in the DB) + mq.setMessageQuotedType(MessageQuotedType.QUOTE_NOT_FOUND); + mq.setId(fakeIds--); + } + } else { + // Message is quoted private group or quoted status + mq.setMessageQuotedType(MessageQuotedType.QUOTE_PRIVACY_GROUP_NOT_FOUND); + // Just set default case if does not match order cases ... + mq.setId(fakeIds--); + String remoteId = mq.getRemoteId(); + if (remoteId != null) { + if (remoteId.compareTo(Message.STATUS_BROADCAST) == 0) { + mq.setMessageQuotedType(MessageQuotedType.QUOTE_STATUS); + } else if (remoteId.contains(Message.GROUP)) { + // Set it first in case if not found + mq.setQuotePrivateGroupName(remoteId); + boolean found = false; + for (Chat cq : idToChat.values()) { + // Find friendly group name and message Id + if (!cq.isGroupChat()) + continue; + + if (cq.getPrintId() != null && remoteId.contains(cq.getPrintId())) { + mq.setQuotePrivateGroupName(cq.getTitle()); + } + + for (Message ori : cq.getMessages()) { + if (ori.getUuid() != null && mq.getUuid() != null + && ori.getUuid().compareTo(mq.getUuid()) == 0) { + mq.setId(ori.getId()); + mq.setMessageQuotedType(MessageQuotedType.QUOTE_PRIVACY_GROUP); + found = true; + break; + } + } + if (found) + break; + } + + } + } + } + m.setMessageQuote(mq); } m.setQuoted(true); @@ -569,6 +636,8 @@ private Map> extractQuoteMessages(Connection conn) throws SQ Map> messagesPerChatId = new HashMap>(); String query = getSelectMessagesQuotesQuery(conn); + boolean hasQuotedProductTable = SQLite3DBParser.containsTable("message_quoted_product", conn); + try (PreparedStatement stmt = conn.prepareStatement(query)) { ResultSet rs = stmt.executeQuery(); while (rs.next()) { @@ -595,7 +664,7 @@ private Map> extractQuoteMessages(Connection conn) throws SQ m.setMediaSize(media_size); m.setLatitude(rs.getDouble("latitude")); //$NON-NLS-1$ m.setLongitude(rs.getDouble("longitude")); //$NON-NLS-1$ - m.setMessageType(decodeMessageType(type, -1, -1, caption, -1, -1, m.getMediaMime())); + m.setMessageType(decodeMessageType(type, -1, -1, caption, -1, -1, -1, m.getMediaMime())); m.setDuration(rs.getInt("media_duration")); //$NON-NLS-1$ if (m.getMessageType() == CONTACT_MESSAGE) { m.setVcards(Arrays.asList(new String[] { Util.getUTF8String(rs, "vcard") })); @@ -610,7 +679,18 @@ private Map> extractQuoteMessages(Connection conn) throws SQ m.setUuid(rs.getString("uuid")); - long chatId = rs.getLong("chatId"); + m.setEditId(rs.getLong("edit_row_id")); + + m.setQuoteChatId(rs.getLong("chatId")); + + m.setRemoteId(rs.getString("remoteId")); + + long chatId = rs.getLong("parent_message_chat_row_id"); + + if (hasQuotedProductTable && m.getMessageType() == PRODUCT_MESSAGE) { + extractQuotedProductInfo(conn, m); + } + List messages = messagesPerChatId.get(chatId); if (messages == null) { messagesPerChatId.put(chatId, messages = new ArrayList()); @@ -623,7 +703,7 @@ private Map> extractQuoteMessages(Connection conn) throws SQ } protected Message.MessageType decodeMessageType(int messageType, int status, Integer edit_version, String caption, - int actionType, int bizStateId, String mediaMime) { + int actionType, int bizStateId, int privacyType, String mediaMime) { Message.MessageType result = UNKNOWN_MESSAGE; switch (messageType) { case 0: @@ -724,6 +804,9 @@ protected Message.MessageType decodeMessageType(int messageType, int status, Int // Can be ignored as nothing was changed. result = EPHEMERAL_SETTINGS_NOT_APPLIED; break; + case 63: + result = SECURITY_NOTIFICATIONS_NO_LONGER_AVAILABLE; + break; // TODO: Handle business related notification (no extra tables/fields) // case 63: // result = ???; @@ -739,11 +822,18 @@ protected Message.MessageType decodeMessageType(int messageType, int status, Int result = EPHEMERAL_DEFAULT; break; case 69: - result = BUSINESS_META_SECURE_SERVICE; + if (privacyType == 1) { + result = MESSAGES_ENCRYPTED; + } else { + result = BUSINESS_META_SECURE_SERVICE; + } break; case 70: result = CALL_MESSAGE; break; + case 76: + result = CONTACTED_FIND_BUSINESSES; + break; case 75: case 108: result = GROUP_ADDED_TO_COMMUNITY; @@ -802,6 +892,9 @@ protected Message.MessageType decodeMessageType(int messageType, int status, Int case 136: result = USER_JOINED_WHATSAPP; break; + case 142: + result = OVER_256_MEMBERS_ONLY_ADMINS_CAN_EDIT; + break; case 155: result = AI_THIRD_PARTY; break; @@ -865,7 +958,7 @@ protected Message.MessageType decodeMessageType(int messageType, int status, Int } else { if (status == 0) { result = DELETED_BY_SENDER; - } else if (status == 4 || status == 5) { + } else if (status == 4 || status == 5 || status == 13) { result = DELETED_MESSAGE; } } @@ -975,7 +1068,11 @@ protected Message.MessageType decodeMessageType(int messageType, int status, Int private static final String SELECT_PRODUCT = "select raw_string as seller, title," + " currency_code as currency, amount_1000 as amount, description" + " from message_product left join jid on business_owner_jid = jid._id where message_row_id=?"; - + + private static final String SELECT_QUOTED_PRODUCT = "select raw_string as seller, title," + + " currency_code as currency, amount_1000 as amount, description" + + " from message_quoted_product left join jid on business_owner_jid = jid._id where message_row_id=?"; + private static final String SELECT_POLL_OPTION = "SELECT option_name as name, vote_total as total FROM message_poll_option where message_row_id=? order by _id"; private static final String SELECT_EPHEMERAL_SETTING = "SELECT setting_duration as duration FROM message_ephemeral_setting where message_row_id=?"; @@ -1005,6 +1102,13 @@ private static String getSelectMessagesQuery(Connection conn) throws SQLExceptio bizStateTableJoin = " left join message_system_initial_privacy_provider msipp on m._id=msipp.message_row_id"; } + String privacyTypeCol = "0"; + String privacyTypeTableJoin = ""; + if (SQLite3DBParser.containsTable("message_system_business_state", conn)) { + privacyTypeCol = "msbs.privacy_message_type"; + privacyTypeTableJoin = " left join message_system_business_state msbs on m._id=msbs.message_row_id"; + } + String grpInvCol = "null"; String grpInvTableJoin = ""; if (SQLite3DBParser.containsTable("message_group_invite", conn)) { @@ -1036,6 +1140,7 @@ private static String getSelectMessagesQuery(Connection conn) throws SQLExceptio + " (m.origination_flags & 1) as forwarded," + " " + mhtCol + " as thumbData2," + " " + bizStateCol + " as bizStateId," + + " " + privacyTypeCol + " as privacyType," + " " + grpInvCol + " as groupInviteName," + " " + sortCol + " as sortId," + " " + uiElemCol + " as uiElem," @@ -1050,6 +1155,7 @@ private static String getSelectMessagesQuery(Connection conn) throws SQLExceptio + " left join message_vcard mv on m._id=mv.message_row_id" + mhtTableJoin + bizStateTableJoin + + privacyTypeTableJoin + grpInvTableJoin + uiElemTableJoin + editTableJoin @@ -1060,20 +1166,27 @@ private static String getSelectMessagesQuotesQuery(Connection conn) throws SQLEx String captionCol = SQLite3DBParser.checkIfColumnExists(conn, "message_quoted_media", "media_caption") ? "mm.media_caption" : "null"; + String editCol = "null as edit_row_id,"; + String editTableJoin = ""; + if (SQLite3DBParser.containsTable("message_edit_info", conn)) { + editCol = "mei.message_row_id as edit_row_id,"; + editTableJoin = " left join message_edit_info mei on mei.original_key_id=mq.key_id"; + } return "select mq.message_row_id as id,mq.chat_row_id as chatId, chatJid.raw_string as remoteId," - + " jid.raw_string as remoteResource, mv.vcard, mq.text_data," + + " jid.raw_string as remoteResource, mv.vcard, mq.text_data, mq.parent_message_chat_row_id," + " mq.from_me as fromMe, mq.timestamp as timestamp, message_url as mediaUrl," + " mm.mime_type as mediaMime, mm.file_length as mediaSize, media_name as mediaName," + " mq.message_type as messageType, latitude, longitude, mm.media_duration, " + captionCol - + " as mediaCaption, mm.file_hash as mediaHash, mm.thumbnail as thumbData," - + " mq.key_id as uuid" + + " as mediaCaption, mm.file_hash as mediaHash, mm.thumbnail as thumbData, " + editCol + + " mq.key_id as uuid" + " from message_quoted mq" + " left join chat on mq.chat_row_id=chat._id" + " left join jid chatJid on chatJid._id=chat.jid_row_id" + " left join message_quoted_media mm on mm.message_row_id=mq.message_row_id" + " left join jid on jid._id=mq.sender_jid_row_id" + " left join message_quoted_location ml on mq.message_row_id=ml.message_row_id" - + " left join message_quoted_vcard mv on mq.message_row_id=mv.message_row_id"; + + " left join message_quoted_vcard mv on mq.message_row_id=mv.message_row_id" + + editTableJoin; } private static String getSelectBlockedQuery(Connection conn) throws SQLException { diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorIOS.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorIOS.java index 950e2c9ae6..a5f8e6215d 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorIOS.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ExtractorIOS.java @@ -95,6 +95,7 @@ import iped.parsers.sqlite.SQLiteUndeleteTable; import iped.parsers.whatsapp.Message.MessageStatus; import iped.parsers.whatsapp.Message.MessageType; +import iped.parsers.whatsapp.Message.MessageQuotedType; import iped.parsers.whatsapp.ProtoBufDecoder.Part; /** @@ -265,7 +266,7 @@ protected List extractChatList() throws WAExtractorException { } // Find quoted messages - findQuotedMessages(c.getMessages(), messagesMap); + findQuotedMessages(c.getMessages(), messagesMap, idToChat); } if (recoverDeletedRecords && !firstTry) { @@ -386,7 +387,7 @@ private void mergeUndeletedMessages(Chat chat, Map messagesMap, } } - private void findQuotedMessages(List messages, Map messagesMap) { + private void findQuotedMessages(List messages, Map messagesMap, Map idToChat) { long fakeIds = 2000000000L; for (Message m : messages) { byte[] metadata = m.getMetaData(); @@ -400,7 +401,9 @@ private void findQuotedMessages(List messages, Map mes List childs = ProtoBufDecoder.findChilds(main, 19); Message messageQuote = messagesMap.get(uuidQuote); - if (messageQuote == null) { + if (messageQuote != null) { + messageQuote.setMessageQuotedType(MessageQuotedType.QUOTE_FOUND); + }else { // Referenced message was deleted, so create a new message and fill with data // extracted from referencing message metadata. messageQuote = new Message(); @@ -566,7 +569,15 @@ private void findQuotedMessages(List messages, Map mes } } break; - + case 30: + type = MessageType.PRODUCT_MESSAGE; + MessageProduct mp = decodeQuotedProductInfo(c, messageQuote); + messageQuote.setProduct(mp); + messageQuote.setMessageQuotedType(MessageQuotedType.QUOTE_CATALOG); + messageQuote.setFromMe(false); + if (mp != null) + messageQuote.setRemoteResource(mp.getSeller()); + break; default: break; } @@ -574,8 +585,49 @@ private void findQuotedMessages(List messages, Map mes } messageQuote.setMessageType(type); - messageQuote.setDeleted(true); + + String contactQuote = ProtoBufDecoder.findString(main, 7); + if (contactQuote != null) { + // find quotes outside this chat + // is the reverse of real msg + messageQuote.setFromMe(!m.isFromMe()); + // set remote resource if is not from me + messageQuote.setRemoteResource(m.getRemoteResource()); + // just set default case if does not match order cases ... + messageQuote.setMessageQuotedType(MessageQuotedType.QUOTE_PRIVACY_GROUP_NOT_FOUND); + if (contactQuote.compareTo(Message.STATUS_BROADCAST) == 0) { + + messageQuote.setMessageQuotedType(MessageQuotedType.QUOTE_STATUS); + + } else if (contactQuote.contains(Message.GROUP)) { + + // set it first in case if not found + messageQuote.setQuotePrivateGroupName(contactQuote); + boolean found = false; + for (Chat cq : idToChat.values()) { + // Find friendly group name and message id + + if (!cq.isGroupChat()) + continue; + + if (cq.getPrintId() != null && contactQuote.contains(cq.getPrintId())) + messageQuote.setQuotePrivateGroupName(cq.getTitle()); + + for (Message origin : cq.getMessages()) { + if (origin.getUuid() != null && origin.getUuid().compareTo(uuidQuote) == 0) { + messageQuote.setId(origin.getId()); + messageQuote.setMessageQuotedType(MessageQuotedType.QUOTE_PRIVACY_GROUP); + found = true; + break; + } + } + if (found) + break; + } + } + } } + if (messageQuote.getThumbData() == null && childs != null) { byte[] thumbData = null; for (Part c : childs) { @@ -924,6 +976,59 @@ private MessageProduct decodeProductInfo(byte[] metadata, Message m) { return null; } + private MessageProduct decodeQuotedProductInfo(Part p2, Message m) { + String title = null; + String observation = null; + String currency = null; + String seller = null; + int amount = 0; + if (p2 != null) { + Part p3 = p2.getChild(1); + if (p3 != null) { + Part p4 = p3.getChild(1); + if (p4 != null) { + Part p5 = p4.getChild(16); + if (p5 != null) { + byte[] bytes = p5.getBytes(); + if (bytes != null) { + m.setThumbData(bytes); + } + } + } + p4 = p3.getChild(3); + if (p4 != null) { + title = p4.getString(); + } + p4 = p3.getChild(4); + if (p4 != null) { + observation = p4.getString(); + } + p4 = p3.getChild(5); + if (p4 != null) { + currency = p4.getString(); + } + p4 = p3.getChild(6); + if (p4 != null) { + String v = p4.getString(); + if (v != null && !v.isBlank()) { + try { + amount = Integer.parseInt(v); + } catch (NumberFormatException e) { + } + } + } + p3 = p2.getChild(2); + if (p3 != null) { + seller = p3.getString(); + } + } + } + if (title != null || currency != null || amount != 0 || seller != null) { + return new MessageProduct(title, seller, currency, amount, observation); + } + return null; + } + private String decodeGroupInvite(byte[] metadata) { Part p1 = new ProtoBufDecoder(metadata).decode(28); if (p1 != null) { diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/Message.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/Message.java index 2eb4e85870..f9fae1a459 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/Message.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/Message.java @@ -30,6 +30,9 @@ public class Message implements Comparable { private static FileChannel fileChannel; private static AtomicLong fileOffset = new AtomicLong(); private static AtomicInteger deletedCounter = new AtomicInteger(); + public static String STATUS_BROADCAST = "status@broadcast"; + public static String GROUP = "@g.us"; + private long id; private int deletedId = -1; @@ -69,7 +72,11 @@ public class Message implements Comparable { private long idQuote; private Message messageQuote = null; private boolean quoted = false; + private MessageQuotedType messageQuotedType = MessageQuotedType.QUOTE_NOT_FOUND; private String uuid = null; + private long editId = -1; + private long quoteChatId = -1; + private String quotePrivateGroupName; private byte[] metaData; private String groupInviteName; private MessageTemplate messageTemplate; @@ -452,6 +459,9 @@ public boolean isSystemMessage() { case USER_REMOVED_FROM_GROUP: case YOU_ADMIN: case YOU_NOT_ADMIN: + case OVER_256_MEMBERS_ONLY_ADMINS_CAN_EDIT: + case SECURITY_NOTIFICATIONS_NO_LONGER_AVAILABLE: + case CONTACTED_FIND_BUSINESSES: return true; default: } @@ -554,6 +564,14 @@ public void setQuoted(boolean quoted) { this.quoted = quoted; } + public MessageQuotedType getMessageQuotedType() { + return messageQuotedType; + } + + public void setMessageQuotedType(MessageQuotedType messageQuotedType) { + this.messageQuotedType = messageQuotedType; + } + public Message getMessageQuote(){ return this.messageQuote; } @@ -570,6 +588,22 @@ public void setUuid(String uuid) { this.uuid = uuid; } + public long getEditId() { + return this.editId; + } + + public void setEditId(long editId) { + this.editId = editId; + } + + public long getQuoteChatId() { + return this.quoteChatId; + } + + public void setQuoteChatId(long quoteChatId) { + this.quoteChatId = quoteChatId; + } + public byte[] getMetaData() { return metaData; } @@ -586,6 +620,14 @@ public void setGroupInviteName(String groupInviteName) { this.groupInviteName = groupInviteName; } + public String getQuotePrivateGroupName() { + return quotePrivateGroupName; + } + + public void setQuotePrivateGroupName(String quotePrivateGroupName) { + this.quotePrivateGroupName = quotePrivateGroupName; + } + public MessageTemplate getMessageTemplate() { return messageTemplate; } @@ -635,13 +677,17 @@ public void setAddress(String address) { } public static enum MessageType { - TEXT_MESSAGE, IMAGE_MESSAGE, AUDIO_MESSAGE, VIDEO_MESSAGE, UNKNOWN_MEDIA_MESSAGE, CONTACT_MESSAGE, LOCATION_MESSAGE, SHARE_LOCATION_MESSAGE, VOICE_CALL, VIDEO_CALL, DOC_MESSAGE, GIF_MESSAGE, BLOCKED_CONTACT, UNBLOCKED_CONTACT, BUSINESS_CHAT, BUSINESS_TO_STANDARD, MESSAGES_ENCRYPTED, MESSAGES_NOW_ENCRYPTED, ENCRYPTION_KEY_CHANGED, MISSED_VOICE_CALL, MISSED_VIDEO_CALL, DELETED_MESSAGE, DELETED_BY_ADMIN, DELETED_BY_SENDER, GROUP_CREATED, USER_ADDED_TO_COMMUNITY, USER_ADDED_TO_GROUP, USER_JOINED_GROUP_FROM_COMMUNITY, USER_JOINED_GROUP_FROM_LINK, USER_JOINED_GROUP_FROM_INVITATION, USER_LEFT_GROUP, USER_REMOVED_FROM_GROUP, USER_COMMUNITY_ADMIN, URL_MESSAGE, GROUP_ICON_CHANGED, GROUP_ICON_DELETED, GROUP_DESCRIPTION_CHANGED, GROUP_DESCRIPTION_DELETED, SUBJECT_CHANGED, YOU_ADMIN, YOU_NOT_ADMIN, USER_ADMIN, WAITING_MESSAGE, STICKER_MESSAGE, REFUSED_VIDEO_CALL, REFUSED_VOICE_CALL, UNAVAILABLE_VIDEO_CALL, UNAVAILABLE_VOICE_CALL, UNKNOWN_VOICE_CALL, UNKNOWN_VIDEO_CALL, VIEW_ONCE_AUDIO_MESSAGE, VIEW_ONCE_IMAGE_MESSAGE, VIEW_ONCE_VIDEO_MESSAGE, CALL_MESSAGE, BUSINESS_META_SECURE_SERVICE, GROUP_INVITE, TEMPLATE_MESSAGE, TEMPLATE_QUOTE, POLL_MESSAGE, EPHEMERAL_DURATION_CHANGED, EPHEMERAL_SETTINGS_NOT_APPLIED, EPHEMERAL_CHANGED, EPHEMERAL_DEFAULT, EPHEMERAL_SAVE, GROUP_CHANGED_ONLY_ADMINS_CAN_ADD, GROUP_CHANGED_ONLY_ADMINS_CAN_SEND, GROUP_CHANGED_ALL_MEMBERS_CAN_SEND, GROUP_CHANGED_ONLY_ADMINS_CAN_EDIT, GROUP_CHANGED_ALL_MEMBERS_CAN_EDIT, GROUP_ONLY_ADMINS_CAN_SEND, CHANGED_DEVICE, CHANGED_NUMBER_TO, CHANGED_NUMBER_CHATTING_WITH_NEW, CHANGED_NUMBER_CHATTING_WITH_OLD, STANDARD_CHAT, SENDER_ADDED_TO_CONTACTS, SENDER_IN_CONTACTS, BUSINESS_OFFICIAL, GROUP_ADDED_TO_COMMUNITY, GROUP_REMOVED_FROM_COMMUNITY, COMMUNITY_MANAGEMENT_ACTION, COMMUNITY_WELCOME, UI_ELEMENTS, UI_ELEMENTS_QUOTE, CHAT_ADDED_PRIVACY, CHANNEL_ADDED_PRIVACY, CHANNEL_CREATED, ORDER_MESSAGE, PRODUCT_MESSAGE, BUSINESS_CHANGED_NAME, USER_JOINED_WHATSAPP, PINNED_MESSAGE, GROUP_NAME_CHANGED, AI_THIRD_PARTY, NEW_PARTICIPANTS_NEED_ADMIN_APPROVAL, RESET_GROUP_LINK, COMMUNITY_RENAMED, ANY_COMMUNITY_MEMBER_CAN_JOIN_GROUP, UNKNOWN_MESSAGE + TEXT_MESSAGE, IMAGE_MESSAGE, AUDIO_MESSAGE, VIDEO_MESSAGE, UNKNOWN_MEDIA_MESSAGE, CONTACT_MESSAGE, LOCATION_MESSAGE, SHARE_LOCATION_MESSAGE, VOICE_CALL, VIDEO_CALL, DOC_MESSAGE, GIF_MESSAGE, BLOCKED_CONTACT, UNBLOCKED_CONTACT, BUSINESS_CHAT, BUSINESS_TO_STANDARD, MESSAGES_ENCRYPTED, MESSAGES_NOW_ENCRYPTED, ENCRYPTION_KEY_CHANGED, MISSED_VOICE_CALL, MISSED_VIDEO_CALL, DELETED_MESSAGE, DELETED_BY_ADMIN, DELETED_BY_SENDER, GROUP_CREATED, USER_ADDED_TO_COMMUNITY, USER_ADDED_TO_GROUP, USER_JOINED_GROUP_FROM_COMMUNITY, USER_JOINED_GROUP_FROM_LINK, USER_JOINED_GROUP_FROM_INVITATION, USER_LEFT_GROUP, USER_REMOVED_FROM_GROUP, USER_COMMUNITY_ADMIN, URL_MESSAGE, GROUP_ICON_CHANGED, GROUP_ICON_DELETED, GROUP_DESCRIPTION_CHANGED, GROUP_DESCRIPTION_DELETED, SUBJECT_CHANGED, YOU_ADMIN, YOU_NOT_ADMIN, USER_ADMIN, WAITING_MESSAGE, STICKER_MESSAGE, REFUSED_VIDEO_CALL, REFUSED_VOICE_CALL, UNAVAILABLE_VIDEO_CALL, UNAVAILABLE_VOICE_CALL, UNKNOWN_VOICE_CALL, UNKNOWN_VIDEO_CALL, VIEW_ONCE_AUDIO_MESSAGE, VIEW_ONCE_IMAGE_MESSAGE, VIEW_ONCE_VIDEO_MESSAGE, CALL_MESSAGE, BUSINESS_META_SECURE_SERVICE, GROUP_INVITE, TEMPLATE_MESSAGE, TEMPLATE_QUOTE, POLL_MESSAGE, EPHEMERAL_DURATION_CHANGED, EPHEMERAL_SETTINGS_NOT_APPLIED, EPHEMERAL_CHANGED, EPHEMERAL_DEFAULT, EPHEMERAL_SAVE, GROUP_CHANGED_ONLY_ADMINS_CAN_ADD, GROUP_CHANGED_ONLY_ADMINS_CAN_SEND, GROUP_CHANGED_ALL_MEMBERS_CAN_SEND, GROUP_CHANGED_ONLY_ADMINS_CAN_EDIT, GROUP_CHANGED_ALL_MEMBERS_CAN_EDIT, GROUP_ONLY_ADMINS_CAN_SEND, CHANGED_DEVICE, CHANGED_NUMBER_TO, CHANGED_NUMBER_CHATTING_WITH_NEW, CHANGED_NUMBER_CHATTING_WITH_OLD, STANDARD_CHAT, SENDER_ADDED_TO_CONTACTS, SENDER_IN_CONTACTS, BUSINESS_OFFICIAL, GROUP_ADDED_TO_COMMUNITY, GROUP_REMOVED_FROM_COMMUNITY, COMMUNITY_MANAGEMENT_ACTION, COMMUNITY_WELCOME, UI_ELEMENTS, UI_ELEMENTS_QUOTE, CHAT_ADDED_PRIVACY, CHANNEL_ADDED_PRIVACY, CHANNEL_CREATED, ORDER_MESSAGE, PRODUCT_MESSAGE, BUSINESS_CHANGED_NAME, USER_JOINED_WHATSAPP, PINNED_MESSAGE, GROUP_NAME_CHANGED, AI_THIRD_PARTY, NEW_PARTICIPANTS_NEED_ADMIN_APPROVAL, RESET_GROUP_LINK, COMMUNITY_RENAMED, ANY_COMMUNITY_MEMBER_CAN_JOIN_GROUP, UNKNOWN_MESSAGE, OVER_256_MEMBERS_ONLY_ADMINS_CAN_EDIT, SECURITY_NOTIFICATIONS_NO_LONGER_AVAILABLE, CONTACTED_FIND_BUSINESSES } public static enum MessageStatus { MESSAGE_UNSENT, MESSAGE_SENT, MESSAGE_DELIVERED, MESSAGE_VIEWED } + public static enum MessageQuotedType { + QUOTE_NOT_FOUND, QUOTE_FOUND, QUOTE_STATUS, QUOTE_PRIVACY_GROUP, QUOTE_PRIVACY_GROUP_NOT_FOUND, QUOTE_CATALOG + } + @Override public int compareTo(Message o) { if (getSortId() != 0 && o.getSortId() != 0) { diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ReportGenerator.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ReportGenerator.java index cef506198a..9d43fd4152 100644 --- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ReportGenerator.java +++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/whatsapp/ReportGenerator.java @@ -22,6 +22,7 @@ import iped.parsers.util.Messages; import iped.parsers.vcard.VCardParser; import iped.parsers.whatsapp.Message.MessageType; +import iped.parsers.whatsapp.Message.MessageQuotedType; import iped.properties.ExtraProperties; import iped.utils.EmojiUtil; import iped.utils.LocalizedFormat; @@ -648,6 +649,18 @@ private synchronized void printMessage(PrintWriter out, Message message, boolean out.println("
"); out.println(Messages.getString("WhatsAppReport.YouNotAdmin") + "
"); break; + case OVER_256_MEMBERS_ONLY_ADMINS_CAN_EDIT: + out.println("
"); + out.println(Messages.getString("WhatsAppReport.Over256MembersOnlyAdminsCanEdit") + "
"); + break; + case SECURITY_NOTIFICATIONS_NO_LONGER_AVAILABLE: + out.println("
"); + out.println(Messages.getString("WhatsAppReport.SecurityNotificationsNoLongerAvailable") + "
"); + break; + case CONTACTED_FIND_BUSINESSES: + out.println("
"); + out.println(Messages.getString("WhatsAppReport.ContactedFindBusinesses", name) + "
"); + break; case USER_ADMIN: out.println("
"); out.print(name + " "); @@ -1243,9 +1256,39 @@ private void printQuote(PrintWriter out, Message message, WAContactsDirectory co } String quoteEnd = "
"; - if (messageQuote.isDeleted()) { - quoteEnd = "
" + String privateGroupName = messageQuote.getQuotePrivateGroupName(); + switch(messageQuote.getMessageQuotedType()){ + case QUOTE_NOT_FOUND: + quoteEnd = "

" + Messages.getString("WhatsAppReport.QuoteNotFound") + "" + quoteEnd; + break; + case QUOTE_STATUS: + quoteEnd = "

" + + Messages.getString("WhatsAppReport.QuoteStaus") + "" + quoteEnd; + break; + case QUOTE_CATALOG: + quoteEnd = "

" + + Messages.getString("WhatsAppReport.QuoteCatalog") + "" + quoteEnd; + break; + case QUOTE_PRIVACY_GROUP: + quoteEnd = "

" + + Messages.getString("WhatsAppReport.QuotePrivacy") + "" + quoteEnd; + if (privateGroupName != null && !privateGroupName.isEmpty()){ + String ms = Messages.getString("WhatsAppReport.QuotePrivacyMessage") + ": "+ privateGroupName +"
" + + Messages.getString("WhatsAppReport.ReferenceId") + " " +messageQuote.getId(); + quoteClick = "onclick=\"showMessage('" + ms + "');\""; + } + break; + case QUOTE_PRIVACY_GROUP_NOT_FOUND: + quoteEnd = "

" + + Messages.getString("WhatsAppReport.QuotePrivacyNotFound") + "" + quoteEnd; + String ms = ""; + if (privateGroupName != null && !privateGroupName.isEmpty()){ + ms = Messages.getString("WhatsAppReport.QuotePrivacyMessage") + ": "+ privateGroupName +"
"; + } + ms += Messages.getString("WhatsAppReport.QuoteNotFound"); + quoteClick = "onclick=\"showMessage('" + ms + "');\""; + break; } switch (messageQuote.getMessageType()) { @@ -1352,7 +1395,20 @@ private void printQuote(PrintWriter out, Message message, WAContactsDirectory co } out.print(quoteEnd); break; - + case PRODUCT_MESSAGE: + MessageProduct product = messageQuote.getProduct(); + String seller = null; + if (product != null) { + seller = getBestContactName(false, product.getSeller(), contactsDirectory, account); + } + out.print("
" + quoteUser + + "
" + formatProduct(product, seller) + quoteEnd); + if (quoteThumb != null) { + out.print("
"); + } + break; default: out.print("
" + quoteUser diff --git a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/css/whatsapp.css b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/css/whatsapp.css index 56473e6f7b..c8cd2e78a5 100644 --- a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/css/whatsapp.css +++ b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/css/whatsapp.css @@ -101,10 +101,46 @@ div.deletedIcon { display: inline-block; width: 16px; height: 16px; + vertical-align: bottom; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACGElEQVR42pWTz2saQRTH39j9oWs09LQW2720ppRe9lQotJce+i/0EPIHBBIqBEJNDh6ChyaX4EEPPSpCTjn03oMBKY1kEdIt2I0FW4kYNdYUZd1dp2+XZnGrsfTBlxmGeZ958+Y7BP5Ep9NJE0Keww0xHo8hHo+/y+fzmcl1MgFQcJBhTiSTSdrtdtcKhUJmCtButxXLsmTDMGYm+/1+yOVy0Gw2KcMwa6lUKuMBtFotxTRNWdf1mYBgMOjOKaWaKIoxDwDJ/7zCRGiRSMQLaDQaCp4uD4fDuZmBQAB4ntei0agXUK/X/6sCSZK8gFqt5lQwGAxmZlDTBMIwIAgC8Bynvd/aevz64GDkAqrVqoI+kFFTyY10GgbHx3B/bw+IKIJ+enr1LZEgY4576+5WVdV5BZT3ZDTQ55UVuHVxAbclCe6srsKP3V3oX16CTsgHF1CpVBR8Htl23N8xOj+Hs40N2yzgwwrHlMKI0vJXw3jpAsrl8twmXh0dgbazAz67KkJoSBSfvVLVkgsolUqOE6+vYPeC4zhnrmsaqNvbYPb7zjpWCuzCQpMRxRcuoFgsOk2c6r5lwcflZRj1erC4tAQP1tfhC1YyaLWAE4QKyWazIYy74XD4DcuyTzGHRzEo3zXk++Eh+/PkhH+4udllQyH9F5rmbH//kT8WSxD8nvdwzxNUBLWICs6A2J2172Z/FNsoPVQT9ek3OcbuBA+nPxcAAAAASUVORK5CYII="); margin: 0; } +div.statusIcon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: bottom; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAWFJREFUOE/Vkr9LAnEUwD/nnZ13aHUcYVqtTkXQcIEE3SFnHdKPoakIWwrEKepoaHANkhbXoIiGHFqFpjaHJhed+gOCmsoWpzhRKDU0nPqOj+/7vPc+7wkM+YQh8/kLYALYAsLAM3DrFe8HmAF2BEFYMwwjnkql0HWdbDb7AKx2AjItenMqSZJmDcPYdBxHdByHSCTSjNdqNWzb7gLspeXRq42RUPOTqo0zXSqiaVqXomq1SjKZrAEG8NkeIV8MRY+W/WozoSH6ECuPPf16HeRyOcrlsgvk24Dzu1D02BwQ4LoulUplH7hsA3bjknIT9yvMiSOMyQFez06xLAtFUX500nLwBCx2SvQCmZOAlp6XZLbrL++qqn5YljXl2U8kEgSDwV8ltqscLknKRdgnct+ol4B1wAZWZFk+ME1TjcViFAqFnmv0ID4gDejANfD2rf9JYKFlf+BD6nvp/S7xHwC+ALvtVBEa/F+QAAAAAElFTkSuQmCC"); + margin: 0; +} + +div.privacyIcon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: bottom; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAZhJREFUOE/Vk0FIWnEcxz/vpZk2dvQsHny0Q6DzIDvMIBFfGVgM2s2dxKOdAj2JN5nu7HlM6iRG+UhCNJCxUgMhGDIhzx5t1WONtxW8ZfaQrvsf///f58P3/4WfwNPjEkVxJxAIuB0Ox/1rv9+nVqt1NE3bBH6MI8IE77NarceFQsFss9lotVqIoojH42E0GhGPx29UVfUDJzo3Kejncjlnu92mWCz+cwuCQDQaxeVykUwmvwMLRoJXkiSd5/N5wuEwmqY9CneXRFEUYrEYg8FAAnp3A+MJVoPB4L7f7yeVShlUA9lslkqlQr1eDwGHk4KILMslr9dLJpMxFKTTaZrNJtVqNQKUxwUm4LMsy++fKfgCfABu9S+8dTtp7H5c4uULC8Ph0DCB3W5ndHnNxlaD7gVvgK+6ILTyGqX8yYdp1mwI65e36g3hxCmHZ9z38Eiwl/cwY5mfKvit/mRtq4PS+a8Fq4nOkw6WfRJHpewi5jnb1A6ur654t93lW48loKGXOAuU/i7JylT64fEAWAd+TS7TM/mHsT8WHKIRDu7JDgAAAABJRU5ErkJggg=="); + margin: 0; +} + +div.privacyDeleteIcon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: bottom; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAHYYAAB2GAV2iE4EAAAGuSURBVDhPjZO/S0JRFMe/76mvtBCyMIRAjUhoyykaqsEhGqMIbHF07Z9wyj/AvaEgaBJyEWoJQkJbWhQagiAxKg2fmd7Oud5nPhXqA+fd8z3v3u/99Z6GUZZ1XT+NxWKroVBIFiqVCvL5/J0Q4oBkWRYVwwZrbrf7OpPJuDweDwqFAsgM0WgU9XodyWTSbLVam9Tvttd9lEo6nRbxeFxQ3g9N00QikRCpVIr1A8VYViKRiMhms3IAaVvQSkQulxPBYJD1MoVEVy0TppcoFougvarSL91uF6VSCeFwmKV8MIMGLofDAdM0lRyl2WzCMAxOJ2WBsAycFHzC/2Wfgsf0b2FjdRFXZ8db8E5PoFqtqrIdv9+PeqOJ3aMr3D9inUo30oXwBGZoY3MmnEYH894pVQZe955l6zsP0PMTPsME9yUDb21hSQyeATTxpbJRLCMNHei0bhp8ydpmMI7ezD0sk5OXJdkyfxowgybvhx8qA2afytq/DJhBE4YGb3NrGbRfG0D17Ru19/bYsJZvYZ2BdY38dVxQ7Eg1BHVWmZzZpsf9zjb4qlQq96xSVQd+AHevnpnE6uxJAAAAAElFTkSuQmCC"); + margin: 0; +} + +div.catalogIcon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: bottom; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAeJJREFUOE+lkzFoE2EYhp/cHY1UEqUYrIFkMBYRC8olqNShktCOLVwEN60uoiAHvVqnbIlQ2kEILoKidNMhQZASEs3kUEocaoUOojYlCNVWuTRpq5eLRGxMyB0q/tv/fh8P3/t+/++g84SAGQu9IWlAobXmsGgcjkajmcioilOq4XFts/Dew1I+QSqVGgJyfwSoqpo5fznBm6KIXoXSuoC5PEEymbQFBADfL3JIVdXp3tNT9HlNDveaFN4KLGZuNQATLRaKwLtdCzNjvv3aKf+h5nRutxtd13/eXS4X5XK5WZsvfuTR6tdpYLIJGL86qzmdfpvs2uWt7RXu3LvYDkin09qJwQjKqsQFt8n1nhpjJQnBAQ+8Bnc3RB7rAmmfQSGfRVGUTkBwMEzsk0Rkr8moy2Tqs0hjxMkDNdK6wIuqQMJjMJ/PWQPC/UeR1koYy4tIx4OYayXqgkh9YAhEqekjl7MBnKlXKPqOUZl7wj7lEptLr+g2DfrkIInnWcqBECPrr9nac9B6gt0MWiM70lXnvtfg5tNnfAic5cbKHN+6PDYWwmHbLTy8FsH4voM8coWNbn8nIB6Pa7Is2wIWZm+zU63gPXmOL1IPsVisbQvDwMBfPYLfTS+BrNVn+ifOfwN+ANvFuxHI5I9YAAAAAElFTkSuQmCC"); + margin: 0; +} /******************** * WhatsApp Top Bar * ********************/ @@ -486,6 +522,13 @@ div:target { font-size: small; } +#conversation .outside { + color: #0000ff; + float: left; + padding-right: 10px; + font-size: small; +} + #conversation .incoming { color: #000000; background-color: #fcfbf6; diff --git a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/js/whatsapp.js b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/js/whatsapp.js index 5a58551291..5f3e192a3c 100644 --- a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/js/whatsapp.js +++ b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/whatsapp/js/whatsapp.js @@ -68,6 +68,12 @@ function createMediaControls() { }); } +function showMessage(message){ + var fragMessageClose = document.getElementById('fragMessageClose').value; + show_prompt("",message,false,fragMessageClose); + return false; +} + function goToAnchorId(id){ var scroll_padding_top = document.getElementById('topbar').getBoundingClientRect().height; var r = document.querySelector(':root');