Skip to content

Instantly share code, notes, and snippets.

@Alexhuszagh
Last active October 4, 2016 19:29
Show Gist options
  • Save Alexhuszagh/3838af51c7fb585270752216a0ce4efa to your computer and use it in GitHub Desktop.
Save Alexhuszagh/3838af51c7fb585270752216a0ce4efa to your computer and use it in GitHub Desktop.
Samsung to Silence SMS
#!/usr/bin/env python
'''
samsung_to_silence.py
---------------------
Convert Samsung SMS plaintext backups (XML) to the silence format.
:warning: This has only been tested with the Samsung Galaxy S6, other
systems may differ.
:author: Alex Huszagh
:license: MIT
:example:
python samsung_to_silence.py --samsung sms.xml
'''
from __future__ import print_function
import argparse
from collections import OrderedDict
import xml.etree.cElementTree as cET
try:
from urllib.parse import unquote
except ImportError:
# Python 2.x
from urllib2 import unquote
# ARGS
# ----
PARSER = argparse.ArgumentParser()
PARSER.add_argument("--samsung",
type=str,
required=True,
help="Path to Samsung SMS XML backup.")
PARSER.add_argument("--silence",
type=str,
default="SilencePlaintextBackup.xml",
help="Path to SIlence export.")
ARGS = PARSER.parse_args()
# CONSTANTS
# ---------
SILENCE_TEMPLATE = OrderedDict([
("protocol", "0"),
("address", "null"),
("date", "null"),
("type", "null"),
("subject", "null"),
("body", "null"),
("toa", "null"),
("sc_toa", "null"),
("service_center", "null"),
("read", "null"),
("status", "-1"),
])
# OBJECTS
# -------
class SamsungParser(object):
'''
XML parser for the Samsung plaintext backup format.
Example:
<file ver="2">
<thread n="94">
<message type="SMS">
<address>18001234567</address>
<body>Message</body>
<date>1475094117922</date>
<read>1</read>
<type>1</type>
<locked>0</locked>
</message>
</thread>
Args:
path Path to XML file
'''
def __init__(self, path):
super(SamsungParser, self).__init__()
self.parser = cET.iterparse(path, events=('end',))
def __iter__(self):
'''High-level wrapper for XML parser'''
try:
return self.parse()
except cET.ParseError:
raise IOError("Non-XML file found")
except TypeError:
raise IOError("XML file not open")
def parse_message(self, element):
'''Extract next SMS from XML backup'''
message = SILENCE_TEMPLATE.copy()
for name in ["address", "body", "date", "read", "type", "locked"]:
message[name] = self.read_content(element, name)
return message
def parse(self):
'''Generator for each SMS item'''
for event, element in self.parser:
if element.tag == "message" and element.attrib["type"] == "SMS":
yield self.parse_message(element)
def unescape(self, message):
'''Unescape URL-encoded text from Samsung'''
items = message.split("+")
unescaped = []
for item in items:
unescaped.append(unquote(item))
return ' '.join(unescaped)
def read_content(self, element, name):
'''Read context from child XML node with given name'''
text = next(element.find(name).itertext())
unescaped = self.unescape(text)
return unescaped.decode("UTF-8")
class SilenceWriter(object):
'''
XML writer for the Silence plaintext backup format.
Example:
<sms address="18001234567" body="Message" date="1475094117922" locked="0" protocol="0" read="1" sc_toa="null" service_center="null" status="-1" subject="null" toa="null" type="1" />
Args:
path Path to output XML file
'''
declaration = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
comment = b'<!-- File Created By SamsungToSilence -->\n'
def __init__(self, path):
super(SilenceWriter, self).__init__()
self.path = path
self.root = cET.Element("smes")
self.tree = cET.ElementTree(self.root)
self.counter = 0
def write(self, message):
'''Write message to XML'''
self.counter += 1
element = cET.SubElement(self.root, "sms")
for key, value in message.items():
element.set(key, value)
def close(self):
'''Close tree and write out to file'''
self.root.set("count", str(self.counter))
with open(self.path, 'wb') as file:
file.write(self.declaration)
file.write(self.comment)
self.tree.write(file, encoding="UTF-8", xml_declaration=False)
# FUNCTIONS
# ---------
def main():
'''Executable entry point'''
parser = SamsungParser(ARGS.samsung)
writer = SilenceWriter(ARGS.silence)
for message in parser:
writer.write(message)
writer.close()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment