Last active
October 4, 2016 19:29
-
-
Save Alexhuszagh/3838af51c7fb585270752216a0ce4efa to your computer and use it in GitHub Desktop.
Samsung to Silence SMS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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