Skip to content
Closed
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
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"python.testing.unittestArgs": [
"-v",
"-s",
".",
"-p",
"*test.py"
],
"python.testing.pytestEnabled": false,
"python.testing.unittestEnabled": true
}
107 changes: 88 additions & 19 deletions package/bin/android-sms-extractor
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ parser.add_argument('--datetime-format', action='store', dest='dt_format',
help="Set datetime (strftime) format for text output (default: %(default)s)",
default="%Y-%m-%d %H:%M:%S")

parser.add_argument('--destination', action='store', dest='destination',
help="Extract only conversations with this normalized destination"
)

parser.add_argument('--show-uris', action='store_true', dest='show_uris',
help="Append attachment URI to placeholder text when available"
)

#===============================================================================
# Parse arguments
#===============================================================================
Expand All @@ -58,9 +66,18 @@ c = db_conn.cursor()
#===============================================================================
# Query and loop through all SMS conversations
#===============================================================================
c.execute("SELECT conversations._id, conversations.name, \
Comment thread
dbollandq3mc3-cpu marked this conversation as resolved.
query = "SELECT conversations._id, conversations.name, \
conversations.participant_normalized_destination \
FROM conversations WHERE latest_message_id NOT NULL ORDER BY name")
FROM conversations WHERE latest_message_id NOT NULL"
params = []

if args.destination:
query += " AND conversations.participant_normalized_destination = ?"
params.append(args.destination)

query += " ORDER BY name"

c.execute(query, params)

for conversation in c.fetchall():
data_id = conversation[0]
Expand All @@ -71,29 +88,81 @@ for conversation in c.fetchall():
print("{} [{}]".format(data_name, data_dest))
print("==================================================")

# Detect available columns in the parts table for improved attachment handling
c.execute("PRAGMA table_info(parts)")
part_columns = [row[1] for row in c.fetchall()]
optional_part_columns = [
"content_type",
"content_uri",
"display_name",
"name",
"filename",
]
available_columns = ["parts.text", "parts.timestamp"] + [
"parts." + name for name in optional_part_columns if name in part_columns
]
select_columns = ", ".join(available_columns)

# Query and loop through all messages in this conversation
c.execute("SELECT messages._id, parts.text, parts.timestamp, \
participants.sim_slot_id FROM messages \
LEFT JOIN parts ON messages._id = parts.message_id \
LEFT JOIN participants ON messages.sender_id = participants._id \
WHERE messages.conversation_id = ? ORDER BY parts.timestamp", (data_id,))
c.execute("SELECT messages._id, {} , participants.sim_slot_id FROM messages "
"LEFT JOIN parts ON messages._id = parts.message_id " \
"LEFT JOIN participants ON messages.sender_id = participants._id "
"WHERE messages.conversation_id = ? ORDER BY parts.timestamp".format(select_columns), (data_id,))

for result in c.fetchall():
msg_id = result[0]
msg_text = result[1]
msg_time = result[2]

# Remove the last 3 digits (milliseconds) from timestamp
msg_time = int(str(msg_time)[:-3])
msg_time_formatted = datetime.fromtimestamp(msg_time).strftime(args.dt_format)

sim_slot_id = result[3]

# A sim_slot_id equal to 0 indicates that the SMS was sent from the device
if sim_slot_id == 0:
msg_direction = "===>"
attachment_type = None
attachment_uri = None
attachment_name = None
attachment_filename = None

if len(result) > 3:
attachment_type = result[3]
if len(result) > 4:
attachment_uri = result[4]
if len(result) > 5:
attachment_name = result[5]
if len(result) > 6:
attachment_filename = result[6]

# Treat all-black block placeholders as absent text for media parts
if msg_text is not None:
trimmed = msg_text.strip()
if trimmed and all(ch in {"\u2588", "\u25A0", "\u2592", "\u2593"} for ch in trimmed):
msg_text = None

# Remove the last 3 digits (milliseconds) from timestamp
msg_time = int(str(msg_time)[:-3])
msg_time_formatted = datetime.fromtimestamp(msg_time).strftime(args.dt_format)

if sim_slot_id == 0:
msg_direction = "===>"
else:
msg_direction = "<---"

if msg_text is None or msg_text == "":
if attachment_type:
if attachment_type.startswith("image/"):
msg_text = "[Image attachment]"
elif attachment_type.startswith("audio/"):
msg_text = "[Audio attachment]"
elif attachment_type.startswith("video/"):
msg_text = "[Video attachment]"
else:
msg_text = "[{} attachment]".format(attachment_type)
if attachment_name:
msg_text += " {}".format(attachment_name)
elif attachment_filename:
msg_text += " {}".format(attachment_filename)
if args.show_uris and attachment_uri:
msg_text += " {}".format(attachment_uri)
elif not attachment_name and not attachment_filename and attachment_uri:
msg_text += " {}".format(attachment_uri)
else:
msg_direction = "<---"
msg_text = ""

print("[{} {}]:\n{}\n".format(
msg_time_formatted, msg_direction, msg_text))
print("[{} {}]:\n{}\n".format(
msg_time_formatted, msg_direction, msg_text))
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ With this Python script you can extract all SMS messages from the SQLite databas
5. Pass the path to the extracted database file to this script:
`$ android-sms-extractor bugle_db`

You can also append attachment information for media parts:
- `$ android-sms-extractor --show-uris bugle_db`

**IMPORTANT:** This script has been written in 2019 when I needed a quick solution to export all SMS from my Android device (*OnePlus X*) running *LineageOS 14.1*. This script was later in 2022 also successfully tested with the SMS database of `com.android.messaging` in *LineageOS 17.1*. If it also works for you: Good! If not: I'm sorry!