Last active
June 13, 2016 00:50
-
-
Save ovatsus/2dd82172dcd8308d559c to your computer and use it in GitHub Desktop.
Windows Phone to Android message importer
This file contains 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
#r "packages/FSharp.Data.2.3.0-beta1/lib/net40/FSharp.Data.dll" | |
#r "System.Xml.Linq.dll" | |
open System | |
open System.Text | |
open FSharp.Data | |
[<Literal>] | |
let androidSample = """ | |
<smses count="40"> | |
<sms protocol="0" address="ToNumber" date="1450377931962" type="2" subject="null" | |
body="Content" toa="null" sc_toa="null" service_center="null" read="1" status="-1" | |
locked="0" date_sent="0" readable_date="17 Dec 2015 6:45:31 p.m." contact_name="ToName" /> | |
<sms protocol="0" address="FromNumber" date="1450378666072" type="1" subject="null" body="Content" | |
toa="null" sc_toa="null" service_center="ServiceCenterNumber" read="1" status="-1" locked="0" | |
date_sent="1450378662000" readable_date="17 Dec 2015 6:57:46 p.m." contact_name="FromName" /> | |
<mms text_only="0" ct_t="application/vnd.wap.multipart.related" msg_box="2" v="18" sub="null" seen="1" | |
rr="nubmer" ct_cls="null" retr_txt_cs="null" ct_l="null" m_size="number" exp="number" sub_cs="null" | |
st="null" creator="com.google.android.apps.messaging" tr_id="T151bb723573" sub_id="1" read="1" | |
resp_st="number" date="1450548803000" m_id="34F545AE-A67C-71E5-981B-005056B920E3" date_sent="0" | |
pri="129" m_type="128" address="ToNumber" d_rpt="129" d_tm="null" read_status="null" locked="0" | |
retr_txt="null" resp_txt="null" rpt_a="null" retr_st="null" m_cls="personal" | |
readable_date="19 Dec 2015 6:13:23 p.m." contact_name="ToName"> | |
<parts> | |
<part seq="-1" ct="application/smil" name="null" chset="null" cd="null" fn="null" cid="<smil>" | |
cl="smil.xml" ctt_s="null" ctt_t="null" text='<smil><head><layout><root-layout/><region id="Image" fit="meet" top="0" left="0" height="100%" width="100%"/></layout></head><body><par dur="5000ms"><img src="image000000.jpg" region="Image" /></par></body></smil>' /> | |
<part seq="0" ct="image/jpeg" name="null" chset="null" cd="null" fn="null" cid="<image000000>" | |
cl="image000000.jpg" ctt_s="null" ctt_t="null" text="null" data="Data" /> | |
</parts> | |
<addrs> | |
<addr address="FromNumber" type="137" charset="106" /> | |
<addr address="ToNumber" type="151" charset="106" /> | |
</addrs> | |
</mms> | |
<mms text_only="0" ct_t="application/vnd.wap.multipart.related" msg_box="1" v="16" sub="null" seen="1" | |
rr="null" ct_cls="null" retr_txt_cs="null" ct_l="http://mms2/mmsc/?A_4OyYlvXyr_aB5LE" m_size="null" | |
exp="null" sub_cs="null" st="null" creator="com.google.android.apps.messaging" tr_id="A_4OyYlvXyr_aB5LE" | |
sub_id="1" read="1" resp_st="null" date="1450548989000" m_id="9D765F0A-A67C-71E5-8938-005056B920E3" | |
date_sent="0" pri="129" m_type="132" address="FromNumber" d_rpt="129" d_tm="null" read_status="null" | |
locked="0" retr_txt="null" resp_txt="null" rpt_a="null" retr_st="null" m_cls="personal" | |
readable_date="19 Dec 2015 6:16:29 p.m." contact_name="FromName"> | |
<parts> | |
<part seq="-1" ct="application/smil" name="smil.smil" chset="106" cd="null" fn="null" | |
cid="<smil.smil>" cl="null" ctt_s="null" ctt_t="null" text='<smil> <head> <layout> <root-layout width="1080" height="1900" /> <region id="Image" width="1080" height="1520" left="0" top="0" fit="meet" /> <region id="Text" width="1080" height="380" left="0" top="1520" /> </layout> </head> <body> <par dur="5000ms" > <img src="image000000.jpg" region="Image"/> </par> <par dur="5000ms" > <img src="image000001.jpg" region="Image"/> </par> <par dur="5000ms" > <text src="text.000002.txt" region="Text"/> </par> </body> </smil> ' /> | |
<part seq="0" ct="image/jpeg" name="image000000.jpg" chset="null" cd="null" fn="null" | |
cid="<image000000.jpg>" cl="image000000.jpg" ctt_s="null" ctt_t="null" text="null" data="DATA" /> | |
<part seq="0" ct="image/jpeg" name="image000001.jpg" chset="null" cd="null" fn="null" | |
cid="<image000001.jpg>" cl="image000001.jpg" ctt_s="null" ctt_t="null" text="null" data="DATA" /> | |
<part seq="0" ct="text/plain" name="text.000002.txt" chset="106" cd="null" fn="null" | |
cid="<text.000002.txt>" cl="text.000002.txt" ctt_s="null" ctt_t="null" text="Content" /> | |
</parts> | |
<addrs> | |
<addr address="FromNumber" type="137" charset="106" /> | |
<addr address="ToNumber" type="151" charset="106" /> | |
</addrs> | |
</mms> | |
</smses> | |
""" | |
[<Literal>] | |
let wpSample = """ | |
<ArrayOfMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> | |
<Message> | |
<Recepients> | |
<string>ToNumber</string> | |
</Recepients> | |
<Body>Content</Body> | |
<IsIncoming>false</IsIncoming> | |
<IsRead>true</IsRead> | |
<Attachments /> | |
<LocalTimestamp>130948371823950673</LocalTimestamp> | |
<Sender /> | |
</Message> | |
<Message> | |
<Recepients /> | |
<Body>Content</Body> | |
<IsIncoming>true</IsIncoming> | |
<IsRead>true</IsRead> | |
<Attachments /> | |
<LocalTimestamp>130948367549678013</LocalTimestamp> | |
<Sender>FromNumber</Sender> | |
</Message> | |
<Message> | |
<Recepients> | |
<string>ToNumber</string> | |
</Recepients> | |
<Body /> | |
<IsIncoming>false</IsIncoming> | |
<IsRead>true</IsRead> | |
<Attachments> | |
<MessageAttachment> | |
<AttachmentContentType>image/jpeg</AttachmentContentType> | |
<AttachmentDataBase64String>Data</AttachmentDataBase64String> | |
</MessageAttachment> | |
<MessageAttachment> | |
<AttachmentContentType>text/plain</AttachmentContentType> | |
<AttachmentDataBase64String>Data</AttachmentDataBase64String> | |
</MessageAttachment> | |
<MessageAttachment> | |
<AttachmentContentType>application/smil</AttachmentContentType> | |
<AttachmentDataBase64String>Data</AttachmentDataBase64String> | |
</MessageAttachment> | |
</Attachments> | |
<LocalTimestamp>130892991988570000</LocalTimestamp> | |
<Sender /> | |
</Message> | |
</ArrayOfMessage> | |
""" | |
type Android = XmlProvider<androidSample> | |
type WP = XmlProvider<wpSample> | |
type MessageType = Sent|Received | |
type MMSPart = | |
| SMIL of xmil: string | |
| Text of text: string | |
| Photo of base64: string | |
type MessageContent = | |
| SMSContent of string | |
| MMSContent of parts: MMSPart[] | |
type Message = | |
Message of messageType: MessageType * | |
phoneNumber: string * | |
date: DateTime * | |
content: MessageContent | |
module DateTime = | |
let unixEpoch = DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks | |
let fromUnixTime androidTime = | |
DateTime(unixEpoch + androidTime * 10000L) | |
let toUnixTime (dotNetTime:DateTime) = | |
(dotNetTime.Ticks - unixEpoch) / 10000L | |
let parseWP (msg:WP.Message) = | |
let messageType, phoneNumber = | |
if msg.IsIncoming then | |
MessageType.Received, msg.Sender.Value | |
else | |
MessageType.Sent, msg.Recepients.String.Value | |
let date = DateTime.FromFileTime msg.LocalTimestamp | |
let content = | |
if msg.Attachments.MessageAttachments.Length = 0 then | |
SMSContent msg.Body.Value | |
else | |
let toText (att:WP.MessageAttachment) = | |
att.AttachmentDataBase64String | |
|> Convert.FromBase64String | |
|> Encoding.Unicode.GetString | |
let toPart (att:WP.MessageAttachment) = | |
match att.AttachmentContentType with | |
| "image/jpeg" -> Some (Photo att.AttachmentDataBase64String) | |
| "text/plain" -> Some (Text (toText att)) | |
| "application/smil" -> Some (SMIL (toText att)) | |
| contentType -> | |
printfn "Unsupported content type %s" contentType | |
None | |
let parts = | |
msg.Attachments.MessageAttachments | |
|> Array.choose toPart | |
MMSContent parts | |
Message(messageType, phoneNumber, date, content) | |
let toAndroidSms (Message(messageType, phoneNumber, date, content)) = | |
let protocol = 0 | |
let smsType = | |
match messageType with | |
| Sent -> 2 | |
| Received -> 1 | |
let subject = "null" | |
let text = | |
match content with | |
| SMSContent text -> text | |
| MMSContent _ -> failwith "Expected SMS, got MMS" | |
let toa = "null" | |
let sc_toa = "null" | |
let serviceCenter = "null" | |
let read = 1 | |
let status = -1 | |
let locked = 0 | |
let dateSent = 0L | |
let readableDate = date.ToString() | |
let contactName = "(Unknown)" | |
Android.Sms( | |
protocol, phoneNumber, DateTime.toUnixTime date, smsType, subject, text, toa, sc_toa, | |
serviceCenter, read, status, locked, dateSent, readableDate, contactName) | |
let toAndroidMms (Message(messageType, phoneNumber, date, content)) = | |
let textOnly = 0 | |
let ctT = "application/vnd.wap.multipart.related" | |
let msgBox, v, mType, addrs = | |
match messageType with | |
| Sent -> 2 , 18, 128, [| Android.Addr("", 137, 106); Android.Addr(phoneNumber, 137, 106) |] | |
| Received -> 1, 16, 132, [| Android.Addr(phoneNumber, 137, 106); Android.Addr("", 137, 106) |] | |
let sub = "null" | |
let seen = 1 | |
let rr = "null" | |
let ctCls = "null" | |
let retrTxtCs = "null" | |
let ctL = "null" | |
let mSize = "null" | |
let exp = "null" | |
let subCs = "null" | |
let st = "null" | |
let creator = "com.google.android.apps.messaging" | |
let trId = "null" | |
let subId = 1 | |
let read = 1 | |
let respSt = "null" | |
let mId = Guid.NewGuid() | |
let dateSent = 0 | |
let pri = 129 | |
let address = phoneNumber | |
let dRpt = 129 | |
let dTm = "null" | |
let readStatus = "null" | |
let locked = 0 | |
let retrTxt = "null" | |
let respTxt = "null" | |
let rptA = "null" | |
let retrSt = "null" | |
let mCls = "personal" | |
let readableDate = date.ToString() | |
let contactName = "(Unknown)" | |
let parts = | |
match content with | |
| SMSContent _ -> failwith "Expected MMS, got SMS" | |
| MMSContent parts -> | |
let toAndroidPart index part = | |
let seq, ct, cl, chset, text, data = | |
match part with | |
| SMIL smil -> | |
-1, | |
"application/smil", | |
"smil.smil", | |
Android.ChsetChoice 106, | |
smil, | |
None | |
| Text text -> | |
0, | |
"text/plain", | |
sprintf "text.%06d.txt" (index - 1), | |
Android.ChsetChoice 106, | |
text, | |
None | |
| Photo base64 -> | |
0, | |
"image/jpeg", | |
sprintf "image%06d.jpg" (index - 1), | |
Android.ChsetChoice "null", | |
"null", | |
Some base64 | |
let name = "null" | |
let cd = "null" | |
let fn = "null" | |
let cid = "<" + cl + ">" | |
let cttS = "null" | |
let cttT = "null" | |
Android.Part(seq, ct, name, chset, cd, fn, cid, cl, cttS, cttT, text, data) | |
parts |> Array.sortBy (function SMIL _ -> 0 | _ -> 1) |> Array.mapi toAndroidPart | |
Android.Mms( | |
textOnly, ctT, msgBox, v, sub, seen, rr, ctCls, retrTxtCs, ctL, mSize, exp, subCs, st, | |
creator, trId, subId, read, respSt, DateTime.toUnixTime date, mId, dateSent, pri, mType, address, dRpt, | |
dTm, readStatus, locked, retrTxt, respTxt, rptA, retrSt, mCls, readableDate, contactName, parts, addrs) | |
let convert (smsInputFile:string) (mmsInputFile:string) (outputFile:string) = | |
let wpSms = WP.Load smsInputFile | |
let wpMms = WP.Load mmsInputFile | |
let count = wpSms.Messages.Length + wpMms.Messages.Length | |
let androidSms = wpSms.Messages |> Array.map (parseWP >> toAndroidSms) | |
let androidMms = wpMms.Messages |> Array.map (parseWP >> toAndroidMms) | |
let android = Android.Smses(count, androidSms, androidMms) | |
android.XElement.Save(outputFile) | |
(* example usage: | |
let smsInputFile = "C:/users/guguer/desktop/sms.msg" | |
let mmsInputFile = "C:/users/guguer/desktop/mms.msg" | |
let outputFile = "C:/users/guguer/desktop/all.xml" | |
convert smsInputFile mmsInputFile outputFile | |
*) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment