diff --git a/scripts/artifact_report.py b/scripts/artifact_report.py index 699308f7..f54ca229 100755 --- a/scripts/artifact_report.py +++ b/scripts/artifact_report.py @@ -369,3 +369,11 @@ def add_chat(self): # Function to add a empty element with the data to be added later (roundabout way to add data to the chat) def add_chat_invisble(self, id, text): self.report_file.write(f'') + + def add_chat_window(self, head, body): + self.report_file.write('
') + self.report_file.write('
') + self.report_file.write(f'{head}') + self.report_file.write('
') + self.report_file.write(f'{body}') + self.report_file.write('
') diff --git a/scripts/artifacts/kleinanzeigen.de.py b/scripts/artifacts/kleinanzeigen.de.py index e60b1a8c..d8ea0e5c 100644 --- a/scripts/artifacts/kleinanzeigen.de.py +++ b/scripts/artifacts/kleinanzeigen.de.py @@ -2,7 +2,9 @@ # Author: Bruno Fischer (@BrunoFischerGermany) # Version: 1.0.0 # https://play.google.com/store/apps/details?id=com.ebay.kleinanzeigen +# kleinanzeigen.de App Version Tested: 15.16.0 from 2023-12-017 # kleinanzeigen.de App Version Tested: 15.23.0 from 2024-03-08 +# kleinanzeigen.de App Version Tested: 15.23.0 from 2024-03-28 # Requirements: xmltodict, json, datetime # # Description: The kleinanzeigen.de app is one of the largest classified ad portals in Germany. There are probably messages between app users. @@ -11,11 +13,12 @@ import xmltodict import json import datetime +import re from scripts.artifact_report import ArtifactHtmlReport from scripts.ilapfuncs import logfunc, tsv, timeline, is_platform_windows, open_sqlite_db_readonly, does_column_exist_in_db, media_to_html -def get_kleinanzeigenrecentsearchescaches(files_found, report_folder, seeker, wrap_text, time_offset): +def get_kleinanzeigenrecentsearchescache(files_found, report_folder, seeker, wrap_text, time_offset): for file_found in files_found: #logfunc(f"{file_found}") if('RECENT_SEARCHES_CACHE' in file_found): @@ -26,11 +29,11 @@ def get_kleinanzeigenrecentsearchescaches(files_found, report_folder, seeker, wr number_search_terms = sum(1 for entry in json_data if 'searchTerm' in entry) for entry in json_data: updated_at = datetime.datetime.utcfromtimestamp(entry['termSearchTimestamp'] / 1000).strftime('%Y-%m-%d %H:%M:%S') - data.append((entry['searchTerm'], entry['category']['localizedName'], )) + data.append((entry['searchTerm'], entry['category']['localizedName'], updated_at)) if(len(data)>0): - report = ArtifactHtmlReport('kleinanzeigen.de - recent search caches') - report.start_artifact_report(report_folder,'kleinanzeigen.de - retent search caches') + report = ArtifactHtmlReport('kleinanzeigen.de - recent search cache') + report.start_artifact_report(report_folder,'kleinanzeigen.de - recent search cache') report.add_script() data_headers = ('Search Term', 'Category', 'Search Timestamp') data_list = [] @@ -44,6 +47,35 @@ def get_kleinanzeigenrecentsearchescaches(files_found, report_folder, seeker, wr else: logfunc("No kleinanzeigen.de - Recent Search Cache data found") +def get_kleinanzeigennonresettablerecentsearchescache(files_found, report_folder, seeker, wrap_text, time_offset): + for file_found in files_found: + #logfunc(f"{file_found}") + if('NON_RESETTABLE_RECENT_SEARCHES_CACHE' in file_found): + logfunc("kleinanzeigen.de - non resettable recent searches cache found") + with open(file_found, encoding='utf-8') as fd: + json_data = json.load(fd) + data = [] + number_search_terms = sum(1 for entry in json_data if 'searchTerm' in entry) + for entry in json_data: + updated_at = datetime.datetime.utcfromtimestamp(entry['termSearchTimestamp'] / 1000).strftime('%Y-%m-%d %H:%M:%S') + data.append((entry['searchTerm'], entry['category']['localizedName'], updated_at)) + + if(len(data)>0): + report = ArtifactHtmlReport('kleinanzeigen.de - non resettable recent search cache') + report.start_artifact_report(report_folder,'kleinanzeigen.de - non resettable recent search cache') + report.add_script() + data_headers = ('Search Term', 'Category', 'Search Timestamp') + data_list = [] + for row in data: + data_list.append((row[0], row[1], row[2])) + report.write_artifact_data_table(data_headers, data_list, file_found, html_escape=False) + report.end_artifact_report() + + tsvname = "kleinanzeigen.de - resettable recent Search Cache Data" + tsv(report_folder, data_headers, data_list,tsvname) + + else: + logfunc("No kleinanzeigen.de - non resettable recent Search Cache data found") def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time_offset): for file_found in files_found: @@ -54,6 +86,7 @@ def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time 'USERPROFILE_INITIALS', # Account Profile Initials 'LAST_EMAIL_USED', # Account Last used Email Address 'AUTH_USER_EMAIL', # Account Authenticated User Email Address + 'AUTH_USER_ID', # Account User ID 'USERPROFILE_PHONE_NUMBER_KEY', # Account Phone Number' 'USERPROFILE_ACCOUNT_TYPE_KEY', # Account Type maybe PRIVATE 'USERPROFILE_USER_SINCE_DATE_KEY', # Account Since - Timestamp - ISO 8601 Format @@ -77,9 +110,9 @@ def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time report = ArtifactHtmlReport('kleinanzeigen.de - account details') report.start_artifact_report(report_folder,'kleinanzeigen.de - account details') report.add_script() - data_headers = ('Account Profile Name', 'Account Profile Initials', 'Account Last Used Email Address', 'Account Authenticated Email Address', 'Account Phone Number', 'Account Type', 'Account Registered since', 'Saved Location Longitude', 'Saved Location Latitude') + data_headers = ('Account Profile Name', 'Account Profile Initials', 'Account Last Used Email Address', 'Account User Id', 'Account Authenticated Email Address', 'Account Phone Number', 'Account Type', 'Account Registered since', 'Saved Location Longitude', 'Saved Location Latitude') data_list = [] - data_list.append((data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8])) + data_list.append((data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9])) report.write_artifact_data_table(data_headers, data_list, file_found, html_escape=False) report.end_artifact_report() @@ -88,7 +121,112 @@ def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time else: logfunc("No kleinanzeigen.de - Account data found") +def get_kleinanzeigenmessagebox(files_found, report_folder, seeker, wrap_text, time_offset): + for file_found in files_found: + file_found = str(file_found) + if(file_found.endswith('messageBoxDatabase.db')): + logfunc('MessageBoxDatabase found') + db = open_sqlite_db_readonly(file_found) + cursor = db.cursor() + cursor.execute(''' + SELECT + json_extract(conversations.counterParty, '$.name') AS "counterparty", + json_extract(conversations.ad, '$.displayTitle') AS "ad title", + json_extract(conversations.ad, '$.identifier') AS "ad number", + json_array_length(conversations.messages) AS count, + datetime(conversations.sortByDate/1000,'unixepoch') AS "Last Message Time", + messages, + json_extract(conversations.counterParty, '$.identifier') AS "counterpartyidentifier" + FROM `conversations` ORDER BY `sortByDate` DESC; + ''') + data = cursor.fetchall() + usageentries = len(data) + if usageentries > 0: + # logfunc(str(data)) + report = ArtifactHtmlReport('kleinanzeigen.de - messagebox') + report.start_artifact_report(report_folder, 'kleinanzeigen.de - messagebox') + report.add_script() + data_headers = ('counterparty', 'ad title', 'ad number', 'number of messages', 'last message time', '') + data_list = [] + for row in data: + # CREATE SINGLE CHAT + singlereportname = f'kleinanzeigen.de - messagebox counterparty {row[0]} - ad-no. {row[2]}' + singlereport = ArtifactHtmlReport(singlereportname) + invalid_chars = '[^a-zA-Z0-9_.-]' + + singlereportfilename = re.sub(invalid_chars, " ", singlereportname) + singlereportfilename = re.sub(r' +', " ", singlereportfilename) + + singlereport.start_artifact_report(report_folder, singlereportfilename) + singlereport.add_script() + singlereport_list = [] + message_data = json.loads(row[5]) + body = '' + for message in message_data: + body += '
\n' + if message['sender'] == "COUNTER_PARTY": + body += '
' + body += row[0][0].upper() + body += '
' + if message['sender'] == "ME": + body += '
' + direction = "outgoing" + elif message['sender'] == "COUNTER_PARTY": + body += '
' + direction = "incoming" + if len(message['text']) == 0: + body += 'message without text
' + elif len(message['text']) > 0: + body += message['text'].replace("\n", "
") + if "attachments" in message and len(message['attachments']) > 0: + body += 'This message has attachments.
' + body += '' + attachment = message["attachments"][0] + if len(attachment['mimeType']) > 0: + body += '
' + body += f'attachment-mimetype: {attachment["mimeType"]}' + if len(attachment['url']) > 0: + body += '
' + body += f'attachment-url: {attachment["url"]}' + body += '
' + if "warning" in message and message['warning'] and len(message['warning']['links']) > 0 and len( + message['warning']['message']) > 0: + warning = message["warning"] + body += '

' + body += 'automatic security message from the app provider shown in the app
"' + for link_name, link_url in warning["links"].items(): + body += warning["message"].replace(link_name, f'{link_name}').replace("\n", "
") + body += '"

' + body += '
' + if message['sender'] == "ME": + body += '

' + elif message['sender'] == "COUNTER_PARTY": + body += '

' + body += message['sortByDate'] + body += '
message-state: ' + body += message['state'].lower() + body += '

' + body += '
' + singlereport_list.append((row[0], row[1], message['text'], direction, message['state'].lower())) + button = '' + if row[3] > 1: + button = f'Open Messages' + + singlereport.add_chat_window(f'counterparty: {row[0]} [identifier: {row[6]}]
ad-title: {row[1]}
ad-no. {row[2]}
{row[3]} messages', body) + + singletsvname = f"kleinanzeigen.de - messages counterparty {row[0]} identifier - {row[6]}] ad-no {row[2]}" + singledata_headers = ('Counterparty', 'Counterparty-Identifier', 'Message-Text', 'Message-Direction', 'Message-State') + tsv(report_folder, singledata_headers, singlereport_list, singletsvname) + data_list.append((row[0], row[1], row[2], row[3], row[4], button)) + report.write_artifact_data_table(data_headers, data_list, file_found, html_escape=False) + report.end_artifact_report() + + tsvname = "kleinanzeigen.de - messagebox" + tsv(report_folder, data_headers, data_list, tsvname) + + else: + logfunc('No messageBox data available') __artifacts_v2__ = { "get_kleinanzeigenaccount": { @@ -103,8 +241,8 @@ def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time "paths": ('*/com.ebay.kleinanzeigen/shared_prefs/com.ebay.kleinanzeigen_preferences.xml'), "function": "get_kleinanzeigenaccount" }, - "get_kleinanzeigenrecentsearchescaches": { - "name": "kleinanzeigen.de App - Recent Searches Cache", + "get_kleinanzeigenrecentsearchescache": { + "name": "kleinanzeigen.de - Recent Searches Cache", "description": "Extracts Recent Searches Cache", "author": "@BrunoFischerGermany", "version": "0.1", @@ -113,6 +251,30 @@ def get_kleinanzeigenaccount(files_found, report_folder, seeker, wrap_text, time "category": "kleinanzeigen.de App", "notes": "", "paths": ('*/com.ebay.kleinanzeigen/files/RECENT_SEARCHES_CACHE'), - "function": "get_kleinanzeigenrecentsearchescaches" + "function": "get_kleinanzeigenrecentsearchescache" + }, + "get_kleinanzeigennonresettablerecentsearchescache": { + "name": "kleinanzeigen.de - Non resettable Recent Searches Cache", + "description": "Extracts Recent Searches Cache", + "author": "@BrunoFischerGermany", + "version": "0.1", + "date": "2024-04-08", + "requirements": "none", + "category": "kleinanzeigen.de App", + "notes": "", + "paths": ('*/com.ebay.kleinanzeigen/files/NON_RESETTABLE_RECENT_SEARCHES_CACHE'), + "function": "get_kleinanzeigennonresettablerecentsearchescache" + }, + "get_kleinanzeigenmessagebox": { + "name": "kleinanzeigen.de - Messagebox", + "description": "Extracts Messages from Database", + "author": "@BrunoFischerGermany", + "version": "0.1", + "date": "2024-04-13", + "requirements": "none", + "category": "kleinanzeigen.de App", + "notes": "", + "paths": ('*com.ebay.kleinanzeigen\databases\messageBoxDatabase.db*'), + "function": "get_kleinanzeigenmessagebox" } } diff --git a/scripts/chat.css b/scripts/chat.css index eb65b7be..9745cb34 100644 --- a/scripts/chat.css +++ b/scripts/chat.css @@ -110,4 +110,76 @@ .card-header { border-top-left-radius: 15px; border-top-right-radius: 15px; +} + +#chatmaster { + display: block; + width: 55vw; + padding: 20px; + background-color: #eee; + margin: 0 auto 0 auto; + font-family: Verdana; + overflow: hidden; +} + +.chatline { + position: relative; + display: inline-block; + width: 100%; +} +.chatme { + padding: 10px; + float: right; + background-color: #a1e214; + border-radius: 15px 15px 0px 15px; + max-width: 80%; +} + +.chatline p { + width: 100%; + float: right; + text-align: right; + margin-top: 5px; + font-size: 70%; + } + + +.chatyou { + padding: 10px; + float: left; + background-color: #fcfcfc; + border-radius: 15px 15px 15px 0px; + margin-left: 50px; + max-width: 80%; +} + +.chatyou_date { + text-align: left !important; + margin-right: -50px; + width: auto; + } + +.chatname { + position: absolute; + display: flex; + background-color: #ccc; + padding: 20px; + bottom: 30px; + left: -20px; + margin: 20px; + width: 25px; + height: 25px; + font-weight: bold; + text-transform: uppercase; + justify-content: center; + align-items: center; + border-radius: 50%; +} +.chathead { + position: relative; + display: inline-block; + width: 100%; + font-weight:bold; + text-align:left; + padding-bottom: 10px; } \ No newline at end of file diff --git a/scripts/report.py b/scripts/report.py index 60b7a01b..28e5bd93 100644 --- a/scripts/report.py +++ b/scripts/report.py @@ -523,6 +523,14 @@ 'KIK - PROFILE PIC': 'image', 'default': 'file-text' }, + 'KLEINANZEIGEN.DE APP': { + 'KLEINANZEIGEN.DE - ACCOUNT DETAILS': 'user', + 'KLEINANZEIGEN.DE - NON RESETTABLE RECENT SEARCHES CACHE': 'search', + 'KLEINANZEIGEN.DE - RECENT SEARCHES CACHE': 'search', + 'KLEINANZEIGEN.DE - MESSAGEBOX': 'message-square', + 'default': 'search', + '_mode': 'search', + }, 'KNOWLEDGEC': { 'KNOWLEDGEC BATTERY LEVEL': 'battery', 'KNOWLEDGEC DEVICE LOCKED': 'lock',