Subversion Repositories Sailfish_Contacts_Restore

Rev

Rev 14 | Rev 16 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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