ContactRestore.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #!/usr/bin/python3
  2. # coding=utf-8
  3. # Doc
  4. # Script to extract Contacts from Sailfish Contact SQLite DB located at
  5. #
  6. #
  7. # Links
  8. # Projects Page: https://wiki.siningsoft.de/doku.php?id=sailfishos:projects:sailfish_contacts_rescue
  9. # FileFormatdescription: https://docs.fileformat.com/email/vcf/#vcf-30-example
  10. # Pytho vobject: http://eventable.github.io/vobject/
  11. # Version
  12. version=0.4
  13. # ChangeLog
  14. # 2021-08-03 - 0.1 - multiple E-Mails with different types are working correctly
  15. # 2021-08-09 - 0.2 - Phonenumbers with parameters, Addresses with parameters, E-Mail-Addresses with marameters
  16. # 2021-08-10 - 0.3 - load Avatars into VCards
  17. # 2021-08-28 - 0.4 - bugfixing after full exporting my contacts
  18. import sqlite3
  19. import vobject
  20. import uuid
  21. import argparse
  22. import os
  23. #class vobject2(vobject):
  24. def DEBUG(debug,msg):
  25. if debug is True:
  26. print("..DEBUG: " + msg)
  27. # will return a default value, when var is None
  28. def setDefault(var, default):
  29. if var is None:
  30. return default
  31. else:
  32. return var
  33. parser = argparse.ArgumentParser(description='Restore SailfishOS 3 Contacts', epilog='This script was written to restore SailfishOS 3 contacts as VCF files. To see additional information, visit: https://wiki.siningsoft.de/doku.php?id=sailfishos:projects:sailfish_contacts_rescue' )
  34. parser.add_argument('--db','-d', required=True, help="Sqlite3 Database file usually /home/{nemo,defaultuser}/.local/share/system/Contacts/qtcontacts-sqlite/contacts.db")
  35. parser.add_argument('--output','-o',required=True, help="Output directory for vcf files")
  36. parser.add_argument('--avatars','-a',required=False, help='Avatar directory. If present otherwise we skip this block of avatars, means, no avatars at all')
  37. parser.add_argument('--debug',action="store_true",help="debugging output to identify problems")
  38. parser.add_argument('--version', action='version', version='%(prog)s ' + str(version))
  39. args = parser.parse_args()
  40. SQLconn = sqlite3.connect(args.db)
  41. try:
  42. SQLContCur = SQLconn.cursor()
  43. for row in SQLContCur.execute('SELECT * FROM Contacts'):
  44. # contactID abfragen
  45. contactID=setDefault(row[0],"NA")
  46. familyN=setDefault(row[6],"NA")
  47. givenN=setDefault(row[4],"NA")
  48. fullN=setDefault(row[1],"NA")
  49. cardfile=args.output + "/" + fullN.replace(" ","_").replace("(","").replace(")","").replace(".","").replace(",","") + ".vcf"
  50. # wir erstellen das Objekt
  51. vcf = vobject.vCard()
  52. vcf.add('uid').value = str(uuid.uuid4())
  53. #vcf.add('uid').value = "Testdaten"
  54. vcf.add('n').value = vobject.vcard.Name( family=familyN, given=givenN )
  55. vcf.add('fn').value =fullN
  56. print("exporting " + fullN + " to file " + cardfile)
  57. DEBUG(args.debug,"Contact " + str(fullN) + " family-name=" + str(familyN) + " given-name=" + str(givenN))
  58. # abfrage der Adressdaten
  59. SQLADRCur = SQLconn.cursor()
  60. for ADRrow in SQLADRCur.execute('SELECT * FROM Addresses JOIN Details on Details.detailId = Addresses.detailId where Addresses.contactId = ' + str(contactID)):
  61. if ADRrow[2] is not None:
  62. ADRstr=str(ADRrow[2])
  63. else:
  64. ADRstr="NA"
  65. if ADRrow[5] is not None:
  66. ADRcit=str(ADRrow[5])
  67. #.replace(" ","\ ").replace("\\", "\\\\").replace(";", "\;").replace(",", "\,")
  68. else:
  69. ADRcit="NA"
  70. if ADRrow[4] is not None:
  71. ADRreg=str(ADRrow[4])
  72. else:
  73. ADRreg="NA"
  74. if ADRrow[6] is not None:
  75. ADRcod=str(ADRrow[6])
  76. else:
  77. ADRcod="NA"
  78. if ADRrow[7] is not None:
  79. ADRcou=str(ADRrow[7])
  80. else:
  81. ADRcou="NA"
  82. # we need to know the context we've read here
  83. if ADRrow[14] is not None:
  84. addADR="ADR;TYPE="+str(ADRrow[14])
  85. else:
  86. addADR="ADR"
  87. vcf.add("ADR").value = vobject.vcard.Address(street=ADRstr, city=ADRcit, region=ADRreg, code=ADRcod,country=ADRcou)
  88. #adr = vcf.add(addADR).value = vobject.vcard.Address()
  89. DEBUG(args.debug,"Addressdata: street=" + ADRstr + " city=" + ADRcit + " region=" + ADRreg + " code=" + ADRcod + " country=" + ADRcou + " context=" + str(ADRrow[14]))
  90. ## Abfragen Organisation
  91. SQLORGCur = SQLconn.cursor()
  92. for ORGrow in SQLORGCur.execute('SELECT * from Organizations where contactId = ' + str(contactID)):
  93. org = vcf.add('ORG').value = [str(ORGrow[2]), str(ORGrow[6])]
  94. if ORGrow[4] is not None:
  95. title = vcf.add('TITLE').value = str(ORGrow[4])
  96. if ORGrow[3] is not None:
  97. role = vcf.add('ROLE').value = str(ORGrow[3])
  98. # Also parameters are possible. Could be read out
  99. # | columnID | column |
  100. # ----------------------------
  101. # | 0 | detailId |
  102. # | 1 | contactId |
  103. # | 2 | name |
  104. # | 3 | role |
  105. # | 4 | title |
  106. # | 5 | location |
  107. # | 6 | department |
  108. # | 7 | logoUrl |
  109. # | 8 | assistantName |
  110. ## Abfragen E-Mail-Adressen
  111. SQLEmailCur = SQLconn.cursor()
  112. for Emailrow in SQLEmailCur.execute('SELECT * from EmailAddresses JOIN Details on Details.detailId= EmailAddresses.detailId where EmailAddresses.contactId = ' + str(contactID)):
  113. # debug ausgabe
  114. DEBUG(args.debug,str(Emailrow[2]) + " at " + str(Emailrow[9]))
  115. email = vcf.add('email')
  116. email.value = str(Emailrow[2])
  117. # nur den Typ einpflegen, wenn das hier nicht none ist
  118. if Emailrow[9] != None:
  119. email.type_param = str(Emailrow[9])
  120. SQLPhoneCur = SQLconn.cursor()
  121. ## Abfragen Telefonnummer, Fax, SMS - Nummern kommen aus der gleichen Tabelle
  122. for Phonerow in SQLPhoneCur.execute('SELECT * from PhoneNumbers JOIN Details on Details.detailId = PhoneNumbers.detailId where PhoneNumbers.contactId = ' + str(contactID)):
  123. # wir müssen die SubTypen unterscheiden
  124. #Null voice
  125. #1 cell
  126. #2 fax
  127. #3 pager
  128. #6 video
  129. #10 Assistent
  130. # None is a normal phone Number
  131. if Phonerow[3] == "1":
  132. phcat='cell'
  133. elif Phonerow[3] == "2":
  134. phcat='fax'
  135. elif Phonerow[3] == "3":
  136. phcat='pager'
  137. elif Phonerow[3] == "6":
  138. phcat='video'
  139. elif Phonerow[3] == "10":
  140. phcat='assistent'
  141. elif Phonerow[3] is None:
  142. phcat='voice'
  143. # debug ausgabe
  144. DEBUG(args.debug,str(Phonerow[2]) + " at " + str(Phonerow[10]) + " as subtype=" + str(Phonerow[3]) + "=" + phcat)
  145. phone = vcf.add(phcat).value = str(Phonerow[2])
  146. # nur den Typ einpflegen, wenn das hier nicht none ist
  147. if Phonerow[10] != None:
  148. try:
  149. phone.type_param = str(Phonerow[10])
  150. except AttributeError:
  151. continue
  152. if args.avatars is not None:
  153. DEBUG(args.debug,"Avatar Argument given")
  154. SQLAVTRCur = SQLconn.cursor()
  155. ## get Avatar Filelink from DB
  156. for AVTRrow in SQLAVTRCur.execute('SELECT imageURL from Avatars where contactId = ' + str(contactID)):
  157. DEBUG(args.debug,"found PHOTO entry")
  158. if AVTRrow[0] is not None:
  159. avatarfile=os.path.split(AVTRrow[0])[1]
  160. # pre-checks
  161. # - is it a file
  162. # - is it jpg
  163. import mimetypes
  164. afile=args.avatars + "/" + avatarfile
  165. DEBUG(args.debug,"Avatar File: " + afile + " mimetype: " + str(mimetypes.guess_type(afile)))
  166. if os.path.isfile(afile) and mimetypes.guess_type(afile)[0] == "image/jpeg":
  167. DEBUG(args.debug,"found file " + afile)
  168. import base64
  169. # actions
  170. # - encode base24 to variable
  171. # - add to vcard
  172. fileopen=open(afile,'rb')
  173. bfile=base64.b64encode(fileopen.read())
  174. fileopen.close()
  175. # https://stackoverflow.com/a/61532783
  176. photo = vcf.add('PHOTO;ENCODING=b;TYPE=image/jpeg')
  177. photo.value = str(bfile.decode('utf-8'))
  178. else:
  179. print("file " + afile + " not found or no JPG")
  180. # Output to file
  181. f = open(cardfile,'w')
  182. f.write(vcf.serialize())
  183. f.close()
  184. # hier brauchen wir einige eception handles -> wie bekommen wir die einzelnen exceptions heruas ?
  185. #except:
  186. #print("Error in executing SQL")
  187. except AttributeError:
  188. print("Datatype mismatch")
  189. raise
  190. # das generöse Except am Ende
  191. except:
  192. print("unhandled error")
  193. raise