Skip to content
Merged

MeWe #643

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
68 changes: 68 additions & 0 deletions scripts/artifacts/L360circlesettings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
__artifacts_v2__ = {
'Life360_CircleSettings': {
'name': 'Life360 CircleSettings',
'description': 'Parses Life360 Circle Settings',
'author': 'Heather Charpentier',
'creation_date': '2026-06-11',
'last_update_date': '2026-06-12',
'requirements': 'none',
'category': 'Life360',
'notes': '',
'paths': ('*/com.life360.android.safetymapd/databases/L360LocalStoreRoomDatabase*',),
'output_types': 'standard',
'artifact_icon': 'circle'
}
}

from datetime import datetime, timezone
from scripts.ilapfuncs import (
artifact_processor,
get_file_path,
get_sqlite_db_records,
logfunc
)

@artifact_processor
def Life360_CircleSettings(context):

data_list = []

files_found = context.get_files_found()
source_path = get_file_path(files_found, 'L360LocalStoreRoomDatabase')

query = '''
SELECT
je.key AS "Circle ID",
json_extract(je.value, '$.featureSetId') AS "App Features",
json_extract(je.value, '$.featureSetRefId') AS "App Features ID",
json_extract(je.value, '$.features.collision_alerts_push') AS "Collision Alerts Push",
json_extract(je.value, '$.features.collision_alerts_sms') AS "Collision Alerts SMS",
json_extract(je.value, '$.features.customer_support') AS "Customer Support",
json_extract(je.value, '$.features.data_breach_detection') AS "Data Breach Detection",
json_extract(je.value, '$.features.driver_reports') AS "Driver Reports",
json_extract(je.value, '$.features.location_history') AS "Location History",
json_extract(je.value, '$.features.place_alerts') AS "Place Alerts",
json_extract(je.value, '$.features.plan') AS "Plan",
json_extract(je.value, '$.features.sos_alerts_push') AS "SOS Alerts Push",
json_extract(je.value, '$.features.sos_alerts_sms') AS "SOS Alerts SMS",
json_extract(je.value, '$.features.tilegps_activation') AS "TileGPS Activation",
json_extract(je.value, '$.features.uber_one') AS "Uber One"
FROM Premium p
CROSS JOIN json_each(p.circleFeatureSetInfo) je;
'''

try:
db_records = get_sqlite_db_records(source_path, query)

logfunc(f'Life360_CircleSettings: Records found = {len(db_records)}')

for record in db_records:

data_list.append((record[0], record[1], record[2], record[3], record[4], record[5], record[6], record[7], record[8], record[9], record[10], record[11], record[12], record[13], record[14]))

except Exception as e:
logfunc(f'Error processing Life360 CircleSettings: {e}')

data_headers = ('Circle ID', 'App Features', 'App Features ID', 'Collision Alerts Push', 'Collision Alerts SMS', 'Customer Support', 'Data Breach Detection', 'Driver Reports', 'Location History', 'Place Alerts', 'Plan', 'SOS Alerts Push', 'SOS Alerts SMS', 'TileGPS Activation', 'Uber One')

return data_headers, data_list, source_path
208 changes: 208 additions & 0 deletions scripts/artifacts/L360driveblade.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# pylint: disable=W0613
__artifacts_v2__ = {
'Life360_Drives': {
'name': 'Life360 Drives',
'description': 'Parses Life360 Drives',
'author': 'Heather Charpentier',
'creation_date': '2026-06-10',
'last_update_date': '2026-06-10',
'requirements': 'none',
'category': 'Life360',
'notes': '',
'paths': ('*/com.life360.android.safetymapd/databases/DriveBladeDB*',),
'output_types': 'standard',
'artifact_icon': 'map-pin'
},
'Life360_DriveEvents': {
'name': 'Life360 Drive Events',
'description': 'Parses Life360 Drive Events',
'author': 'Heather Charpentier',
'creation_date': '2026-06-10',
'last_update_date': '2026-06-10',
'requirements': 'none',
'category': 'Life360',
'notes': '',
'paths': ('*/com.life360.android.safetymapd/databases/DriveBladeDB*',),
'output_types': ['html', 'tsv', 'lava', 'kml'],
'artifact_icon': 'map-pin'
},
'Life360_DriveWaypoints': {
'name': 'Life360 Drive Waypoints',
'description': 'Parses Life360 Drive Waypoints',
'author': 'Heather Charpentier',
'creation_date': '2026-06-10',
'last_update_date': '2026-06-10',
'requirements': 'none',
'category': 'Life360',
'notes': '',
'paths': ('*/com.life360.android.safetymapd/databases/DriveBladeDB*',),
'output_types': ['html', 'tsv', 'lava', 'kml'],
'artifact_icon': 'map-pin'
}
}

import datetime
import sqlite3

from scripts.ilapfuncs import artifact_processor, open_sqlite_db_readonly, logfunc


def _ms_to_utc(value):
if not value:
return ''
try:
return datetime.datetime.fromtimestamp(int(value) / 1000, datetime.timezone.utc)
except (ValueError, OverflowError, OSError, TypeError):
return ''


def _find(context, suffix):
for file_found in context.get_files_found():
file_found = str(file_found)
if file_found.endswith(suffix):
return file_found
return ''


def _q(cursor, sql):
try:
cursor.execute(sql)
return cursor.fetchall()
except sqlite3.Error:
return []


@artifact_processor
def Life360_Drives(context):
source = _find(context, 'DriveBladeDB')
data_list = []

if source:
db = open_sqlite_db_readonly(source)
cursor = db.cursor()

for row in _q(cursor, '''
SELECT
driveId,
userId,
startTime,
endTime,
topSpeed,
topSpeed * 2.23694,
averageSpeed,
averageSpeed * 2.23694,
distance,
duration,
speedingCount,
hardBrakingCount,
rapidAccelerationCount,
distractedCount,
crashCount,
score,
updatedAt
FROM Drives
ORDER BY startTime
'''):
data_list.append((
row[0], row[1],
_ms_to_utc(row[2]), _ms_to_utc(row[3]),
row[4], row[5], row[6], row[7],
row[8], row[9],
row[10], row[11], row[12], row[13], row[14],
row[15], _ms_to_utc(row[16])
))
db.close()

data_headers = (
'Drive ID', 'User ID',
('Start Time', 'datetime'), ('End Time', 'datetime'),
'Top Speed MPS', 'Top Speed MPH',
'Average Speed MPS', 'Average Speed MPH',
'Distance', 'Duration',
'Speeding Events', 'Hard Braking Events',
'Rapid Acceleration Events', 'Distracted Events', 'Crash Events',
'Score', ('Updated At', 'datetime')
)
return data_headers, data_list, source


@artifact_processor
def Life360_DriveEvents(context):
source = _find(context, 'DriveBladeDB')
data_list = []

if source:
db = open_sqlite_db_readonly(source)
cursor = db.cursor()

for row in _q(cursor, '''
SELECT
DriveEvents.eventId,
DriveEvents.driveId,
Drives.userId,
DriveEvents.eventTime,
DriveEvents.eventType,
DriveEvents.lat,
DriveEvents.lon,
DriveEvents.speed,
DriveEvents.speed * 2.23694,
DriveEvents.accuracy
FROM DriveEvents
LEFT JOIN Drives ON Drives.driveId = DriveEvents.driveId
ORDER BY DriveEvents.eventTime
'''):
data_list.append((
row[0], row[1], row[2],
_ms_to_utc(row[3]),
row[4], row[5], row[6],
row[7], row[8], row[9]
))
db.close()

data_headers = (
'Event ID', 'Drive ID', 'User ID',
('Event Time', 'datetime'),
'Event Type', 'Latitude', 'Longitude',
'Speed MPS', 'Speed MPH', 'Accuracy'
)
return data_headers, data_list, source


@artifact_processor
def Life360_DriveWaypoints(context):
source = _find(context, 'DriveBladeDB')
data_list = []

if source:
db = open_sqlite_db_readonly(source)
cursor = db.cursor()

for row in _q(cursor, '''
SELECT
DriveWaypoints.driveId,
Drives.userId,
DriveWaypoints.timestamp,
DriveWaypoints.lat,
DriveWaypoints.lon,
DriveWaypoints.speed,
DriveWaypoints.speed * 2.23694,
DriveWaypoints.accuracy
FROM DriveWaypoints
LEFT JOIN Drives ON Drives.driveId = DriveWaypoints.driveId
ORDER BY DriveWaypoints.timestamp
'''):
data_list.append((
row[0], row[1],
_ms_to_utc(row[2]),
row[3], row[4],
row[5], row[6], row[7]
))
db.close()

data_headers = (
'Drive ID', 'User ID',
('Timestamp', 'datetime'),
'Latitude', 'Longitude',
'Speed MPS', 'Speed MPH', 'Accuracy'
)
return data_headers, data_list, source
Loading