Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 57 additions & 68 deletions scripts/artifacts/discordAcct.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,63 @@
import gzip
import re
import os
import json
import shutil
import errno
from pathlib import Path
import string
__artifacts_v2__ = {
"get_discordAcct": { # This should match the function name exactly
"name": "Discord - Account",
"description": "Parses Discord accounts",
"author": "",
"creation_date": "",
"last_updated": "2025-11-25",
"requirements": "none",
"category": "Discord",
"notes": "",
"paths": ('*/mobile/Containers/Data/Application/*/Documents/mmkv/mmkv.default'),
"output_types": "standard", # or ["html", "tsv", "timeline", "lava"]
}
}

from packaging import version
from scripts.artifact_report import ArtifactHtmlReport
from scripts.ilapfuncs import logfunc, logdevinfo, timeline, tsv, is_platform_windows
import string
from scripts.ilapfuncs import artifact_processor, logfunc

def strings(filename, min=4):
with open(filename, errors="ignore") as f: # Python 3.x
def strings(filename, min_length=4):
with open(filename, "rb") as f: # Python 3.x
# with open(filename, "rb") as f: # Python 2.x
result = ""
for c in f.read():
if c in string.printable:
result += c
continue
if len(result) >= min:
yield result
result = ""
if len(result) >= min: # catch result at EOF
yield result

def get_discordAcct(files_found, report_folder, seeker, wrap_text, timezone_offset):
searchlist = []
for file_found in files_found:
file_found = str(file_found)

for s in strings(file_found):
#print(type(s))
#print(s)
searchlist.append(str(s),)
result = ""
for c in f.read():
char = chr(c)
if char in string.printable:
result += char
continue
if len(result) >= min_length:
yield result
result = ""
if len(result) >= min_length: # catch result at EOF
yield result

counter = 0
data_list = []
for x in searchlist:
counter += 1
if 'user_id_cache' in x:
#print(x)
wf = searchlist[counter].split('"')
try:
data_list.append(('USER_ID_CACHE', wf[1]))
except:
pass

if 'email_cache' in x:
#print(x)
wfa = searchlist[counter].split('"')
try:
data_list.append(('EMAIL_CACHE', wfa[1]))
except:
pass
@artifact_processor
def get_discordAcct(context):
data_list = []
data_headers = ('Key Name', 'Data Value', 'Source File')
for file_found in context.get_files_found():
file_found = str(file_found)
searchlist = []
for s in strings(file_found):
searchlist.append(str(s),)

if len(data_list) > 0:
report = ArtifactHtmlReport('Discord Account')
report.start_artifact_report(report_folder, 'Discord Account')
report.add_script()
data_headers = ('Key', 'Value')
report.write_artifact_data_table(data_headers, data_list, file_found)
report.end_artifact_report()

tsvname = 'Discord Account'
tsv(report_folder, data_headers, data_list, tsvname)
counter = 0
for x in searchlist:
counter += 1
if 'user_id_cache' in x:
wf = searchlist[counter].split('"')
try:
data_list.append(('USER_ID_CACHE', wf[1], file_found))
except (IndexError, TypeError) as e:
logfunc(f"Error parsing user_id_cache: {str(e)}")

if 'email_cache' in x:
#print(x)
wfa = searchlist[counter].split('"')
try:
data_list.append(('EMAIL_CACHE', wfa[1], file_found))
except (IndexError, TypeError) as e:
logfunc(f"Error parsing email_cache: {str(e)}")

__artifacts__ = {
"discordacct": (
"Discord",
('*/mobile/Containers/Data/Application/*/Documents/mmkv/mmkv.default'),
get_discordAcct)
}
return data_headers, data_list, 'see Source File for more info'

31 changes: 14 additions & 17 deletions scripts/artifacts/discordChats.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Parses Discord chat messages from FSCacheData and \'a\' database",
"author": "Original Unknown, John Hyla & @stark4n6",
"creation_date": "",
"last_updated": "2025-06-23",
"last_updated": "2025-11-25",
"requirements": "none",
"category": "Discord",
"notes": "",
Expand All @@ -21,12 +21,12 @@
import os
import re

from scripts.ilapfuncs import artifact_processor, logfunc, logdevinfo, media_to_html, get_resolution_for_model_id, get_file_path, get_sqlite_db_records
from scripts.ilapfuncs import artifact_processor, logfunc, media_to_html, get_resolution_for_model_id, get_file_path, get_sqlite_db_records

@artifact_processor
def discordChats(files_found, report_folder, seeker, wrap_text, timezone_offset):

pathedhead = pathedtail = ''
def discordChats(context):
files_found = context.get_files_found()
report_folder = context.get_report_folder()

def reduceSize(width: int, height: int, max_width: int, max_height: int) -> (int, int):
if width > height:
Expand Down Expand Up @@ -166,7 +166,7 @@ def process_json(jsonfinal):

y = y + 1

pathedhead, pathedtail = os.path.split(pathed)
_, pathedtail = os.path.split(pathed)

if timestamp == '':
pass
Expand All @@ -192,15 +192,15 @@ def process_json(jsonfinal):
model_id = parsed_data.get('ProductType')

if not model_id:
logfunc(f"Cannot detect model ID. Cannot link attachments")
logfunc("Cannot detect model ID. Cannot link attachments")
break
resolution = get_resolution_for_model_id(model_id)

break
if not activation_record_found:
logfunc(f'activation_record.plist not found. Unable to determine model/resolution for attachment linking')
logfunc('activation_record.plist not found. Unable to determine model/resolution for attachment linking')
if not resolution:
logfunc(f"Cannot link attachments due to missing resolution")
logfunc("Cannot link attachments due to missing resolution")

data_list = []

Expand All @@ -211,21 +211,17 @@ def process_json(jsonfinal):

try:
if not file_found.endswith('activation_record.plist') and os.path.isfile(file_found) and file_found != source_path:
with open(file_found, "r") as f_in:
with open(file_found, "r", encoding="utf-8") as f_in:
for jsondata in f_in:
jsonfinal = json.loads(jsondata)
if isinstance(jsonfinal, list):
jsonfinal = jsonfinal[0]
process_json(jsonfinal)
else:
pass
elif source_path:
query = '''select data from messages0'''
#data_headers = (('Message Timestamp', 'datetime'), ('Edited Timestamp', 'datetime'),'Sender Username','Sender Global Name','Sender ID','Message','Attachment(s)','Message Type','Call Ended','Message ID','Channel ID',)

db_records = get_sqlite_db_records(source_path, query)
for record in db_records:
attach_name = message_type = call_end = sender_id = ''
blob_data = record[0]
if len(blob_data) > 1:
blob_data = blob_data[1:]
Expand All @@ -235,8 +231,9 @@ def process_json(jsonfinal):
process_json(jsonfinal)

except ValueError as e:
pass
#logfunc('JSON error: %s' % e)
logfunc(f"Error parsing JSON from {file_found}: {str(e)}")

data_headers = ('Timestamp','Edited Timestamp','Username','Bot?','Content','Attachments','User ID','Channel ID','Embedded Author','Author URL','Author Icon URL','Embedded URL','Embedded Script','Footer Text', 'Footer Icon URL', 'Source File')
data_headers = (('Timestamp', 'datetime'), ('Edited Timestamp', 'datetime'), 'Username', 'Bot?', 'Content', 'Attachments',
'User ID', 'Channel ID', 'Embedded Author', 'Author URL', 'Author Icon URL', 'Embedded URL', 'Embedded Script',
'Footer Text', 'Footer Icon URL', 'Source File')
return data_headers, data_list, 'See source file(s) below:'
63 changes: 30 additions & 33 deletions scripts/artifacts/discordManifest.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
__artifacts_v2__ = {
"get_discordManifest": { # This should match the function name exactly
"name": "Discord - Manifest",
"description": "Parses Discord manifest",
"author": "",
"creation_date": "",
"last_updated": "2025-11-25",
"requirements": "none",
"category": "Discord",
"notes": "",
"paths": ('*/mobile/Containers/Data/Application/*/Documents/RCTAsyncLocalStorage_V1/manifest.json'),
"output_types": "standard", # or ["html", "tsv", "timeline", "lava"]
}
}

import os
import json
from pathlib import Path
from scripts.ilapfuncs import artifact_processor

from packaging import version
from scripts.artifact_report import ArtifactHtmlReport
from scripts.ilapfuncs import logfunc, logdevinfo, timeline, tsv, is_platform_windows
@artifact_processor
def get_discordManifest(context):
data_list = []
for file_found in context.get_files_found():
file_found = str(file_found)

jsonfinal = None
if os.path.isfile(file_found):
with open(file_found, encoding='utf-8') as f_in:
for jsondata in f_in:
jsonfinal = json.loads(jsondata)

def get_discordManifest(files_found, report_folder, seeker, wrap_text, timezone_offset):
data_list = []
for file_found in files_found:
file_found = str(file_found)

if os.path.isfile(file_found):
with open(file_found) as f_in:
for jsondata in f_in:
jsonfinal = json.loads(jsondata)
if jsonfinal:
for key, value in jsonfinal.items():
data_list.append((key, value, file_found))

for key, value in jsonfinal.items():
data_list.append((key, value))
data_headers = ('Key Name', 'Data Value')


if len(data_list) > 0:
report = ArtifactHtmlReport('Discord Manifest')
report.start_artifact_report(report_folder, 'Discord Manifest')
report.add_script()
data_headers = ('Key', 'Value')
report.write_artifact_data_table(data_headers, data_list, file_found)
report.end_artifact_report()

tsvname = 'Discord Manifest'
tsv(report_folder, data_headers, data_list, tsvname)

__artifacts__ = {
"discordmanifest": (
"Discord",
('*/mobile/Containers/Data/Application/*/Documents/RCTAsyncLocalStorage_V1/manifest.json'),
get_discordManifest)
}
return data_headers, data_list, 'see Source File for more'
Loading