class ViBrowserWidget(QtGui.QWidget): def __init__(self, cursor, path, daemon=False): QtGui.QWidget.__init__(self) self.ui = Ui_ViberBrowser() self.ui.setupUi(self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.cursor = cursor self.backup_path = path filename = plugins_utils.realFileName(self.cursor, filename="Contacts.data", domaintype="AppDomain", path="Documents", domain="com.viber") if filename == '': filename = plugins_utils.realFileName(self.cursor, filename="Contacts.data", domaintype="AppDomain") self.fname_contacts = os.path.join(self.backup_path, filename) # check if files exist if (not os.path.isfile(self.fname_contacts)): raise Exception("Viber database not found: \"%s\"" % self.fname_chatstorage) if (daemon == False): self.populateUI() # signal-slot chats/msgs connection QtCore.QObject.connect(self.ui.chatsWidget, QtCore.SIGNAL("itemSelectionChanged()"), self.onChatsClick) self.ui.chatsWidget.setColumnHidden(0, True) self.ui.msgsWidget.setColumnHidden(0, True) # signal-slot connection: right click context menu on contacts table self.ui.contactsWidget.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.contactsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuContacts) # signal-slot connection: right click context menu on calls table self.ui.callsWidget.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.callsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuCalls) # signal-slot connection: right click context menu on chats table self.ui.chatsWidget.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.chatsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuChats) # signal-slot connection: right click context menu on messages table self.ui.msgsWidget.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.msgsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuMsgs) ''' Populates the Viber Browser widget ''' def populateUI(self): ###################################################### # CONTACTS SECTION # ###################################################### contacts = self.getContacts() self.ui.contactsWidget.setRowCount(len(contacts)) self.ui.contactsWidget.setSortingEnabled(False) row = 0 for contact in contacts: if hasattr(contact, 'ZMAINNAME') and hasattr( contact, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if contact.ZPREFIXNAME is not None: nameStr += contact.ZPREFIXNAME + " " if contact.ZMAINNAME is not None: nameStr += contact.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole, nameStr) self.ui.contactsWidget.setItem(row, 0, newItem) if hasattr(contact, 'ZCANONIZEDPHONENUM'): newItem = QtGui.QTableWidgetItem( str(contact.ZCANONIZEDPHONENUM)) self.ui.contactsWidget.setItem(row, 1, newItem) if hasattr(contact, 'ZREGISTRATIONDATE'): newItem = QtGui.QTableWidgetItem() if contact.ZREGISTRATIONDATE is not None: newItem.setData( QtCore.Qt.DisplayRole, str(self.formatDate(contact.ZREGISTRATIONDATE))) self.ui.contactsWidget.setItem(row, 2, newItem) if hasattr(contact, 'ZMODIFCATIONDATE'): newItem = QtGui.QTableWidgetItem() if contact.ZMODIFCATIONDATE is not None: newItem.setData( QtCore.Qt.DisplayRole, str(self.formatDate(contact.ZMODIFCATIONDATE))) self.ui.contactsWidget.setItem(row, 3, newItem) if hasattr(contact, 'ZISVIBERICON') and hasattr( contact, 'ZICONID'): newItem = QtGui.QTableWidgetItem() iconRealFilename = "" iconlocalfile = "" if contact.ZISVIBERICON is 1 and contact.ZICONID is not None: iconlocalfile = contact.ZICONID + ".jpg" iconRealFilename = os.path.join( self.backup_path, plugins_utils.realFileName(self.cursor, filename=iconlocalfile, domaintype="AppDomain")) # add a thumnail to the table view icon = QtGui.QIcon(iconRealFilename) newItem.setIcon(icon) # add info for attachment export (ctx menu) newItem.setData(QtCore.Qt.UserRole, iconRealFilename) newItem.setData(QtCore.Qt.UserRole + 1, iconlocalfile) self.ui.contactsWidget.setItem(row, 4, newItem) self.ui.contactsWidget.setRowHeight(row, 80) row = row + 1 self.ui.contactsWidget.setSortingEnabled(True) self.ui.contactsWidget.setIconSize(QtCore.QSize(80, 80)) self.ui.contactsWidget.resizeColumnsToContents() self.ui.contactsWidget.setColumnWidth(4, 80) ###################################################### # RECENT CALLS SECTION # ###################################################### calls = self.getCalls() self.ui.callsWidget.setRowCount(len(calls)) self.ui.callsWidget.setSortingEnabled(False) row = 0 for call in calls: if hasattr(call, 'ZMAINNAME') and hasattr(call, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if call.ZPREFIXNAME is not None: nameStr += call.ZPREFIXNAME + " " if call.ZMAINNAME is not None: nameStr += call.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole, nameStr) self.ui.callsWidget.setItem(row, 0, newItem) if hasattr(call, 'ZDATE'): newItem = QtGui.QTableWidgetItem() if call.ZDATE is not None: newItem.setData(QtCore.Qt.DisplayRole, str(self.formatDate(call.ZDATE))) self.ui.callsWidget.setItem(row, 1, newItem) if hasattr(call, 'ZDURATION'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole, call.ZDURATION) self.ui.callsWidget.setItem(row, 2, newItem) if hasattr(call, 'ZCALLTYPE'): newItem = QtGui.QTableWidgetItem(str(call.ZCALLTYPE)) self.ui.callsWidget.setItem(row, 3, newItem) if 'missed' in str(call.ZCALLTYPE): for i in range(4): self.ui.callsWidget.item(row, i).setBackground( QtCore.Qt.red) elif 'incoming' in str(call.ZCALLTYPE): for i in range(4): self.ui.callsWidget.item(row, i).setBackground( QtCore.Qt.cyan) else: for i in range(4): self.ui.callsWidget.item(row, i).setBackground( QtCore.Qt.green) row = row + 1 self.ui.callsWidget.setSortingEnabled(True) self.ui.callsWidget.resizeColumnsToContents() self.ui.callsWidget.resizeRowsToContents() ###################################################### # CHATS SECTION # ###################################################### chats = self.getChats() self.ui.chatsWidget.setRowCount(len(chats)) row = 0 for chat in chats: if hasattr(chat, 'Z_PK'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole, chat.Z_PK) self.ui.chatsWidget.setItem(row, 0, newItem) if hasattr(chat, 'ZMAINNAME') and hasattr(chat, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if chat.ZPREFIXNAME is not None: nameStr += chat.ZPREFIXNAME + " " if chat.ZMAINNAME is not None: nameStr += chat.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole, nameStr) self.ui.chatsWidget.setItem(row, 1, newItem) if hasattr(chat, 'ZNAME'): newItem = QtGui.QTableWidgetItem(chat.ZNAME) self.ui.chatsWidget.setItem(row, 2, newItem) if hasattr(chat, 'ZGROUPID'): newItem = QtGui.QTableWidgetItem(chat.ZGROUPID) self.ui.chatsWidget.setItem(row, 3, newItem) if hasattr(chat, 'ZDATE'): newItem = QtGui.QTableWidgetItem( str(self.formatDate(chat.ZDATE))) self.ui.chatsWidget.setItem(row, 4, newItem) if hasattr(chat, 'ZUNREADCOUNT'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole, chat.ZUNREADCOUNT) self.ui.chatsWidget.setItem(row, 5, newItem) if hasattr(chat, 'ZGROUPID'): if chat.ZGROUPID is not None: for i in range(6): self.ui.chatsWidget.item(row, i).setBackground( QtCore.Qt.yellow) row = row + 1 self.ui.chatsWidget.resizeColumnsToContents() self.ui.chatsWidget.resizeRowsToContents() def formatDate(self, mactime): # if timestamp is not like "304966548", but like "306350664.792749", # then just use the numbers in front of the "." mactime = str(mactime) if mactime.find(".") > -1: mactime = mactime[:mactime.find(".")] date_time = datetime.fromtimestamp(int(mactime) + 11323 * 60 * 1440) return date_time ######################################################################## # DB QUERIES # ######################################################################## ''' Function to fetch contacts related info ''' def getContacts(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZABCONTACT - ZPHONENUMBERINDEX ** zphonenumberindex_columns = "ZPHONENUMBERINDEX.Z_PK, ZPHONENUMBERINDEX.ZREGISTRATIONDATE, ZPHONENUMBERINDEX.ZCANONIZEDPHONENUM, ZPHONENUMBERINDEX.ZICONID" zabcontact_columns = "ZABCONTACT.ZISVIBERICON, ZABCONTACT.ZMODIFCATIONDATE, ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZPHONENUMBERINDEX.ZISVIBER = 1 AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zphonenumberindex_columns + ", " + zabcontact_columns + " FROM ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches contacts namedtuple contacts = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return contacts ''' Function to fetch recent calls related info ''' def getCalls(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZRECENT - ZRECENTSLINE ** -> ZPHONENUMBERINDEX -> ZABCONTACT zrecent_columns = "ZRECENT.ZDURATION, ZRECENT.ZDATE, ZRECENT.ZCALLTYPE" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZRECENT.ZRECENTSLINE = ZRECENTSLINE.Z_PK AND ZRECENTSLINE.ZPHONENUMINDEX = ZPHONENUMBERINDEX.Z_PK AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zrecent_columns + ", " + zabcontact_columns + " FROM ZRECENT, ZRECENTSLINE, ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches calls namedtuple calls = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return calls ''' Function to fetch conversations related info ''' def getChats(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZCONVERSATION ** -> Z_3PHONENUMINDEXES -> ZPHONENUMBERINDEX -> ZABCONTACT zconversation_columns = "ZCONVERSATION.Z_PK, ZCONVERSATION.ZDATE, ZCONVERSATION.ZUNREADCOUNT, ZCONVERSATION.ZGROUPID, ZCONVERSATION.ZNAME" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZCONVERSATION.Z_PK = Z_3PHONENUMINDEXES.Z_3CONVERSATIONS AND Z_3PHONENUMINDEXES.Z_5PHONENUMINDEXES = ZPHONENUMBERINDEX.Z_PK AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zconversation_columns + ", " + zabcontact_columns + " FROM ZCONVERSATION, Z_3PHONENUMINDEXES, ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches chats namedtuple chats = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return chats ''' Function to fetch messages ''' def getMsgs(self, zconversation): # refresh the window #(the selected row is highligthed and the chats table appears disabled) QtGui.QApplication.processEvents() # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZVIBERMESSAGE ** - ZPHONENUMBERINDEX zvibermessage_columns = "ZVIBERMESSAGE.Z_PK, ZVIBERMESSAGE.ZATTACHMENT, ZVIBERMESSAGE.ZLOCATION, ZVIBERMESSAGE.ZPHONENUMINDEX, ZVIBERMESSAGE.ZDATE, ZVIBERMESSAGE.ZSTATEDATE, ZVIBERMESSAGE.ZSTATE, ZVIBERMESSAGE.ZTEXT" conditions = "ZVIBERMESSAGE.ZCONVERSATION=?;" query = "SELECT " + zvibermessage_columns + " FROM ZVIBERMESSAGE WHERE " + conditions self.tempcur.execute(query, [zconversation]) messages = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return messages ''' Function to fetch messages (threaded) ''' def getMsgsThreaded(self, zconversation): # progress window progress = QtGui.QProgressDialog("Querying the database ...", "Abort", 0, 0, self) progress.setWindowTitle("Viber Browser ...") progress.setWindowModality(QtCore.Qt.WindowModal) progress.setMinimumDuration(0) progress.setCancelButton(None) progress.show() # ** ZVIBERMESSAGE ** - ZPHONENUMBERINDEX zvibermessage_columns = "ZVIBERMESSAGE.Z_PK, ZVIBERMESSAGE.ZATTACHMENT, ZVIBERMESSAGE.ZLOCATION, ZVIBERMESSAGE.ZPHONENUMINDEX, ZVIBERMESSAGE.ZDATE, ZVIBERMESSAGE.ZSTATEDATE, ZVIBERMESSAGE.ZSTATE, ZVIBERMESSAGE.ZTEXT" conditions = "ZVIBERMESSAGE.ZCONVERSATION=?;" query = "SELECT " + zvibermessage_columns + " FROM ZVIBERMESSAGE WHERE " + conditions # call a thread to query the db showing a progress bar queryTh = ThreadedQuery(self.fname_contacts, query, [zconversation]) queryTh.start() while queryTh.isAlive(): QtGui.QApplication.processEvents() progress.close() messages = queryTh.getResult() # a namedtuple is returned return messages ''' Function to fetch msgs contact info ''' def getMsgContact(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZPHONENUMBERINDEX - ZABCONTACT zphonenumberindex_columns = "ZPHONENUMBERINDEX.ZCANONIZEDPHONENUM" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZPHONENUMBERINDEX.Z_PK=? AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zphonenumberindex_columns + ", " + zabcontact_columns + " FROM ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query, [zpk]) msgcontact = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return msgcontact ''' Function to fetch location info ''' def getLocation(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZVIBERLOCATION table query = "SELECT * FROM ZVIBERLOCATION WHERE Z_PK=?;" self.tempcur.execute(query, [zpk]) location = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return location ''' Function to fetch attachment info ''' def getMediaItem(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s" % sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZATTACHMENT table query = "SELECT * FROM ZATTACHMENT WHERE Z_PK=?;" self.tempcur.execute(query, [zpk]) media = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return media ######################################################################## # SLOTS # ######################################################################## def onChatsClick(self): # disable chats table (to disable click events while processing) self.ui.chatsWidget.setEnabled(False) # retrieving selected row self.ui.chatsWidget.setCurrentCell(self.ui.chatsWidget.currentRow(), 0) currentSelectedItem = self.ui.chatsWidget.currentItem() if (currentSelectedItem): pass else: return ###################################################### # MESSAGES SECTION # ###################################################### zpk = int(currentSelectedItem.text()) #msgs = self.getMsgs(zpk) # <--- single thread msgs = self.getMsgsThreaded(zpk) # <--- multithreaded # re-select a visible column to allow the keyboard selection self.ui.chatsWidget.setCurrentCell(self.ui.chatsWidget.currentRow(), 1) # erase previous messages and set new table lenght #self.ui.msgsWidget.clearContents() self.ui.msgsWidget.setSortingEnabled(False) self.ui.msgsWidget.setRowCount(len(msgs)) row = 0 for msg in msgs: from_me = False # var to remember from whom is the msg if hasattr(msg, 'Z_PK'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole, msg.Z_PK) self.ui.msgsWidget.setItem(row, 0, newItem) if hasattr(msg, 'ZPHONENUMINDEX'): newItem = QtGui.QTableWidgetItem() fromstring = "" if msg.ZPHONENUMINDEX is not None: msgcontact = self.getMsgContact(msg.ZPHONENUMINDEX) if msgcontact is not None: if msgcontact.ZPREFIXNAME is not None: fromstring += msgcontact.ZPREFIXNAME + " " if msgcontact.ZMAINNAME is not None: fromstring += msgcontact.ZMAINNAME + " " if msgcontact.ZCANONIZEDPHONENUM is not None: fromstring += msgcontact.ZCANONIZEDPHONENUM else: fromstring = "N/A" else: fromstring = "Me" from_me = True newItem.setData(QtCore.Qt.DisplayRole, fromstring) self.ui.msgsWidget.setItem(row, 1, newItem) if hasattr(msg, 'ZDATE'): newItem = QtGui.QTableWidgetItem( str(self.formatDate(msg.ZDATE))) self.ui.msgsWidget.setItem(row, 2, newItem) if hasattr(msg, 'ZTEXT'): newItem = QtGui.QTableWidgetItem(msg.ZTEXT) self.ui.msgsWidget.setItem(row, 3, newItem) if hasattr(msg, 'ZSTATE'): newItem = QtGui.QTableWidgetItem(msg.ZSTATE) self.ui.msgsWidget.setItem(row, 5, newItem) if hasattr(msg, 'ZSTATEDATE'): newItem = QtGui.QTableWidgetItem( str(self.formatDate(msg.ZSTATEDATE))) self.ui.msgsWidget.setItem(row, 6, newItem) # ATTACHMENTS SECTION if hasattr(msg, 'ZATTACHMENT') and hasattr(msg, 'ZLOCATION'): mediaItem = QtGui.QTableWidgetItem("") msgcontent = "" # case 1: msg contains a location if msg.ZLOCATION is not None: location = self.getLocation(msg.ZLOCATION) msgcontent += "GPS\n" + "lat: " + str( location.ZLATITUDE) + "\nlong: " + str( location.ZLONGITUDE) + "\naddress: " + unicode( location.ZADDRESS) + "\n" # re-set message content (3rd column) newItem = QtGui.QTableWidgetItem(msgcontent) self.ui.msgsWidget.setItem(row, 3, newItem) # compose location thumbnail filename (guess) # example: if LAT = 41.796141 and LONG = 12.481714 # the filename is 41.796141_12.jpg strlat = str(location.ZLATITUDE) strlong = str(location.ZLONGITUDE) strlat_s = strlat.split('.') strlong_s = strlong.split('.') filename = strlat_s[0] + "." + ( strlat_s[1])[:6] + "_" + strlong_s[0] + ".jpg" locationlocalfile = filename locationRealFilename = os.path.join( self.backup_path, plugins_utils.realFileName(self.cursor, filename=locationlocalfile, domaintype="AppDomain")) # add a thumbnail to the table view icon = QtGui.QIcon(locationRealFilename) mediaItem.setIcon(icon) # add info for attachment export (ctx menu) mediaItem.setData(QtCore.Qt.UserRole + 2, location.ZLATITUDE) mediaItem.setData(QtCore.Qt.UserRole + 3, location.ZLONGITUDE) # case 2: msg contains an attachment elif msg.ZATTACHMENT is not None: media = self.getMediaItem(msg.ZATTACHMENT) msgcontent += "ATTACHMENT\n" + "type: " + str( media.ZTYPE) + "\nstate: " + str( media.ZSTATE) + "\nsize: " + str( media.ZFILESIZE) + "B\n" if 'sticker' in media.ZTYPE: msgcontent += "id: " + media.ZID + "\n" # re-set message content (3rd column) newItem = QtGui.QTableWidgetItem(msgcontent) self.ui.msgsWidget.setItem(row, 3, newItem) if media.ZNAME: medialocalfile = media.ZNAME mediaRealFilename = os.path.join( self.backup_path, plugins_utils.realFileName(self.cursor, filename=medialocalfile, domaintype="AppDomain")) # add a thumbnail to the table view icon = QtGui.QIcon(mediaRealFilename) mediaItem.setIcon(icon) # add info for attachment export (ctx menu) mediaItem.setData(QtCore.Qt.UserRole, mediaRealFilename) mediaItem.setData(QtCore.Qt.UserRole + 1, medialocalfile) self.ui.msgsWidget.setItem(row, 4, mediaItem) if from_me: for i in range(7): self.ui.msgsWidget.item(row, i).setBackground(QtCore.Qt.green) row = row + 1 self.ui.msgsWidget.setSortingEnabled(True) self.ui.msgsWidget.setIconSize(QtCore.QSize(150, 150)) self.ui.msgsWidget.resizeColumnsToContents() self.ui.msgsWidget.setColumnWidth(6, 150) self.ui.msgsWidget.resizeRowsToContents() # signal-slot connection: preserve row height when sorting messages self.ui.msgsWidget.horizontalHeader().sortIndicatorChanged.connect( self.ui.msgsWidget.resizeRowsToContents) # re-enable chats table self.ui.chatsWidget.setEnabled(True) self.ui.chatsWidget.setFocus() ###################################################### # CTX MENU SECTION # ###################################################### def ctxMenuMsgs(self, pos): cell = self.ui.msgsWidget.itemAt(pos) self.link = cell.data(QtCore.Qt.UserRole) self.name = cell.data(QtCore.Qt.UserRole + 1) self.lat = cell.data(QtCore.Qt.UserRole + 2) self.long = cell.data(QtCore.Qt.UserRole + 3) menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVmsgs) menu.addAction(action1) if self.link != None: menu.addSeparator() action1 = QtGui.QAction("Open attachment in standard viewer", self) action1.triggered.connect(self.openWithViewer) menu.addAction(action1) action1 = QtGui.QAction("Export attachment", self) action1.triggered.connect(self.exportSelectedFile) menu.addAction(action1) if (self.lat and self.long) is not None: menu.addSeparator() action1 = QtGui.QAction("Show GPS coordinates on Google Maps", self) action1.triggered.connect(self.openGPSBrowser) menu.addAction(action1) menu.exec_(self.ui.msgsWidget.mapToGlobal(pos)) def ctxMenuContacts(self, pos): cell = self.ui.contactsWidget.itemAt(pos) if cell is not None: self.link = cell.data(QtCore.Qt.UserRole) self.name = cell.data(QtCore.Qt.UserRole + 1) menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVcontacts) menu.addAction(action1) if self.link != None: menu.addSeparator() action1 = QtGui.QAction("Open contact icon in standard viewer", self) action1.triggered.connect(self.openWithViewer) menu.addAction(action1) action1 = QtGui.QAction("Export contact icon", self) action1.triggered.connect(self.exportSelectedFile) menu.addAction(action1) menu.exec_(self.ui.contactsWidget.mapToGlobal(pos)) def ctxMenuCalls(self, pos): menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVcalls) menu.addAction(action1) menu.exec_(self.ui.callsWidget.mapToGlobal(pos)) def ctxMenuChats(self, pos): menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVchats) menu.addAction(action1) menu.exec_(self.ui.chatsWidget.mapToGlobal(pos)) ##### ATTACHMENTS EXPORT FUNCTIONS ##### def openWithViewer(self): if sys.platform.startswith('linux'): subprocess.call(["xdg-open", self.link]) else: os.startfile(self.link) def exportSelectedFile(self): filename = QtGui.QFileDialog.getSaveFileName(self, "Export attachment", self.name) filename = filename[0] if (len(filename) == 0): return try: shutil.copy(self.link, filename) QtGui.QMessageBox.about(self, "Confirm", "Attachment saved as %s." % filename) except: QtGui.QMessageBox.about(self, "Error", "Error while saving attachment") def openGPSBrowser(self): coordinatesURL = "https://maps.google.com/?q=" + str( self.lat) + "," + str(self.long) webbrowser.open(coordinatesURL) ##### TABLES EXPORT FUNCTIONS ##### def exportCSVcontacts(self): self.exportCSVtable(self.ui.contactsWidget) def exportCSVcalls(self): self.exportCSVtable(self.ui.callsWidget) def exportCSVchats(self): self.exportCSVtable(self.ui.chatsWidget) def exportCSVmsgs(self): self.exportCSVtable(self.ui.msgsWidget) def exportCSVtable(self, table): filename = QtGui.QFileDialog.getSaveFileName(self, "Export table", "table", ".csv") filename = filename[0] if (len(filename) == 0): return f = open(filename, 'w') # header tablerow = '"' for c in range(table.columnCount()): hitem = table.horizontalHeaderItem(c) if hitem is not None: tablerow += unicode(hitem.text()).encode('utf8') tablerow += '","' tablerow = tablerow[:-2] + "\n" f.write(tablerow) # content tablerow = '"' for r in range(table.rowCount()): for c in range(table.columnCount()): item = table.item(r, c) if item is not None: tablerow += unicode(item.text().replace( '\n', ' ')).encode('utf8') tablerow += '","' tablerow = tablerow[:-2] + "\n" f.write(tablerow) tablerow = '"' f.close()
class ViBrowserWidget(QtGui.QWidget): def __init__(self, cursor, path, daemon = False): QtGui.QWidget.__init__(self) self.ui = Ui_ViberBrowser() self.ui.setupUi(self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.cursor = cursor self.backup_path = path filename = plugins_utils.realFileName(self.cursor, filename="Contacts.data", domaintype="AppDomain", path="Documents", domain="com.viber") if filename == '': filename = plugins_utils.realFileName(self.cursor, filename="Contacts.data", domaintype="AppDomain") self.fname_contacts = os.path.join(self.backup_path, filename) # check if files exist if (not os.path.isfile(self.fname_contacts)): raise Exception("Viber database not found: \"%s\""%self.fname_chatstorage) if (daemon == False): self.populateUI() # signal-slot chats/msgs connection QtCore.QObject.connect(self.ui.chatsWidget, QtCore.SIGNAL("itemSelectionChanged()"), self.onChatsClick) self.ui.chatsWidget.setColumnHidden(0,True) self.ui.msgsWidget.setColumnHidden(0,True) # signal-slot connection: right click context menu on contacts table self.ui.contactsWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.connect(self.ui.contactsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuContacts) # signal-slot connection: right click context menu on calls table self.ui.callsWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.connect(self.ui.callsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuCalls) # signal-slot connection: right click context menu on chats table self.ui.chatsWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.connect(self.ui.chatsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuChats) # signal-slot connection: right click context menu on messages table self.ui.msgsWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.connect(self.ui.msgsWidget, QtCore.SIGNAL('customContextMenuRequested(QPoint)'), self.ctxMenuMsgs) ''' Populates the Viber Browser widget ''' def populateUI(self): ###################################################### # CONTACTS SECTION # ###################################################### contacts = self.getContacts() self.ui.contactsWidget.setRowCount(len(contacts)) self.ui.contactsWidget.setSortingEnabled(False) row = 0 for contact in contacts: if hasattr(contact, 'ZMAINNAME') and hasattr(contact, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if contact.ZPREFIXNAME is not None: nameStr += contact.ZPREFIXNAME + " " if contact.ZMAINNAME is not None: nameStr += contact.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole,nameStr) self.ui.contactsWidget.setItem(row, 0, newItem) if hasattr(contact, 'ZCANONIZEDPHONENUM'): newItem = QtGui.QTableWidgetItem(str(contact.ZCANONIZEDPHONENUM)) self.ui.contactsWidget.setItem(row, 1, newItem) if hasattr(contact, 'ZREGISTRATIONDATE'): newItem = QtGui.QTableWidgetItem() if contact.ZREGISTRATIONDATE is not None: newItem.setData(QtCore.Qt.DisplayRole,str(self.formatDate(contact.ZREGISTRATIONDATE))) self.ui.contactsWidget.setItem(row, 2, newItem) if hasattr(contact, 'ZMODIFCATIONDATE'): newItem = QtGui.QTableWidgetItem() if contact.ZMODIFCATIONDATE is not None: newItem.setData(QtCore.Qt.DisplayRole,str(self.formatDate(contact.ZMODIFCATIONDATE))) self.ui.contactsWidget.setItem(row, 3, newItem) if hasattr(contact, 'ZISVIBERICON') and hasattr(contact, 'ZICONID'): newItem = QtGui.QTableWidgetItem() iconRealFilename = "" iconlocalfile = "" if contact.ZISVIBERICON is 1 and contact.ZICONID is not None: iconlocalfile = contact.ZICONID + ".jpg" iconRealFilename = os.path.join(self.backup_path, plugins_utils.realFileName(self.cursor, filename=iconlocalfile, domaintype="AppDomain")) # add a thumnail to the table view icon = QtGui.QIcon(iconRealFilename) newItem.setIcon(icon) # add info for attachment export (ctx menu) newItem.setData(QtCore.Qt.UserRole, iconRealFilename) newItem.setData(QtCore.Qt.UserRole+1, iconlocalfile) self.ui.contactsWidget.setItem(row, 4, newItem) self.ui.contactsWidget.setRowHeight(row, 80) row = row + 1 self.ui.contactsWidget.setSortingEnabled(True) self.ui.contactsWidget.setIconSize(QtCore.QSize(80,80)) self.ui.contactsWidget.resizeColumnsToContents() self.ui.contactsWidget.setColumnWidth(4, 80) ###################################################### # RECENT CALLS SECTION # ###################################################### calls = self.getCalls() self.ui.callsWidget.setRowCount(len(calls)) self.ui.callsWidget.setSortingEnabled(False) row = 0 for call in calls: if hasattr(call, 'ZMAINNAME') and hasattr(call, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if call.ZPREFIXNAME is not None: nameStr += call.ZPREFIXNAME + " " if call.ZMAINNAME is not None: nameStr += call.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole,nameStr) self.ui.callsWidget.setItem(row, 0, newItem) if hasattr(call, 'ZDATE'): newItem = QtGui.QTableWidgetItem() if call.ZDATE is not None: newItem.setData(QtCore.Qt.DisplayRole,str(self.formatDate(call.ZDATE))) self.ui.callsWidget.setItem(row, 1, newItem) if hasattr(call, 'ZDURATION'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole,call.ZDURATION) self.ui.callsWidget.setItem(row, 2, newItem) if hasattr(call, 'ZCALLTYPE'): newItem = QtGui.QTableWidgetItem(str(call.ZCALLTYPE)) self.ui.callsWidget.setItem(row, 3, newItem) if 'missed' in str(call.ZCALLTYPE): for i in range(4): self.ui.callsWidget.item(row,i).setBackground(QtCore.Qt.red) elif 'incoming' in str(call.ZCALLTYPE): for i in range(4): self.ui.callsWidget.item(row,i).setBackground(QtCore.Qt.cyan) else: for i in range(4): self.ui.callsWidget.item(row,i).setBackground(QtCore.Qt.green) row = row + 1 self.ui.callsWidget.setSortingEnabled(True) self.ui.callsWidget.resizeColumnsToContents() self.ui.callsWidget.resizeRowsToContents() ###################################################### # CHATS SECTION # ###################################################### chats = self.getChats() self.ui.chatsWidget.setRowCount(len(chats)) row = 0 for chat in chats: if hasattr(chat, 'Z_PK'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole,chat.Z_PK) self.ui.chatsWidget.setItem(row, 0, newItem) if hasattr(chat, 'ZMAINNAME') and hasattr(chat, 'ZPREFIXNAME'): newItem = QtGui.QTableWidgetItem() nameStr = "" if chat.ZPREFIXNAME is not None: nameStr += chat.ZPREFIXNAME + " " if chat.ZMAINNAME is not None: nameStr += chat.ZMAINNAME newItem.setData(QtCore.Qt.DisplayRole,nameStr) self.ui.chatsWidget.setItem(row, 1, newItem) if hasattr(chat, 'ZNAME'): newItem = QtGui.QTableWidgetItem(chat.ZNAME) self.ui.chatsWidget.setItem(row, 2, newItem) if hasattr(chat, 'ZGROUPID'): newItem = QtGui.QTableWidgetItem(chat.ZGROUPID) self.ui.chatsWidget.setItem(row, 3, newItem) if hasattr(chat, 'ZDATE'): newItem = QtGui.QTableWidgetItem(str(self.formatDate(chat.ZDATE))) self.ui.chatsWidget.setItem(row, 4, newItem) if hasattr(chat, 'ZUNREADCOUNT'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole,chat.ZUNREADCOUNT) self.ui.chatsWidget.setItem(row, 5, newItem) if hasattr(chat, 'ZGROUPID'): if chat.ZGROUPID is not None: for i in range(6): self.ui.chatsWidget.item(row,i).setBackground(QtCore.Qt.yellow) row = row + 1 self.ui.chatsWidget.resizeColumnsToContents() self.ui.chatsWidget.resizeRowsToContents() def formatDate(self, mactime): # if timestamp is not like "304966548", but like "306350664.792749", # then just use the numbers in front of the "." mactime = str(mactime) if mactime.find(".") > -1: mactime = mactime[:mactime.find(".")] date_time = datetime.fromtimestamp(int(mactime)+11323*60*1440) return date_time ######################################################################## # DB QUERIES # ######################################################################## ''' Function to fetch contacts related info ''' def getContacts(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZABCONTACT - ZPHONENUMBERINDEX ** zphonenumberindex_columns = "ZPHONENUMBERINDEX.Z_PK, ZPHONENUMBERINDEX.ZREGISTRATIONDATE, ZPHONENUMBERINDEX.ZCANONIZEDPHONENUM, ZPHONENUMBERINDEX.ZICONID" zabcontact_columns = "ZABCONTACT.ZISVIBERICON, ZABCONTACT.ZMODIFCATIONDATE, ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZPHONENUMBERINDEX.ZISVIBER = 1 AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zphonenumberindex_columns + ", " + zabcontact_columns + " FROM ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches contacts namedtuple contacts = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return contacts ''' Function to fetch recent calls related info ''' def getCalls(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZRECENT - ZRECENTSLINE ** -> ZPHONENUMBERINDEX -> ZABCONTACT zrecent_columns = "ZRECENT.ZDURATION, ZRECENT.ZDATE, ZRECENT.ZCALLTYPE" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZRECENT.ZRECENTSLINE = ZRECENTSLINE.Z_PK AND ZRECENTSLINE.ZPHONENUMINDEX = ZPHONENUMBERINDEX.Z_PK AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zrecent_columns + ", " + zabcontact_columns + " FROM ZRECENT, ZRECENTSLINE, ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches calls namedtuple calls = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return calls ''' Function to fetch conversations related info ''' def getChats(self): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZCONVERSATION ** -> Z_3PHONENUMINDEXES -> ZPHONENUMBERINDEX -> ZABCONTACT zconversation_columns = "ZCONVERSATION.Z_PK, ZCONVERSATION.ZDATE, ZCONVERSATION.ZUNREADCOUNT, ZCONVERSATION.ZGROUPID, ZCONVERSATION.ZNAME" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZCONVERSATION.Z_PK = Z_3PHONENUMINDEXES.Z_3CONVERSATIONS AND Z_3PHONENUMINDEXES.Z_5PHONENUMINDEXES = ZPHONENUMBERINDEX.Z_PK AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zconversation_columns + ", " + zabcontact_columns + " FROM ZCONVERSATION, Z_3PHONENUMINDEXES, ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query) # fetches chats namedtuple chats = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return chats ''' Function to fetch messages ''' def getMsgs(self, zconversation): # refresh the window #(the selected row is highligthed and the chats table appears disabled) QtGui.QApplication.processEvents() # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ** ZVIBERMESSAGE ** - ZPHONENUMBERINDEX zvibermessage_columns = "ZVIBERMESSAGE.Z_PK, ZVIBERMESSAGE.ZATTACHMENT, ZVIBERMESSAGE.ZLOCATION, ZVIBERMESSAGE.ZPHONENUMINDEX, ZVIBERMESSAGE.ZDATE, ZVIBERMESSAGE.ZSTATEDATE, ZVIBERMESSAGE.ZSTATE, ZVIBERMESSAGE.ZTEXT" conditions = "ZVIBERMESSAGE.ZCONVERSATION=?;" query = "SELECT " + zvibermessage_columns + " FROM ZVIBERMESSAGE WHERE " + conditions self.tempcur.execute(query, [zconversation]) messages = self.tempcur.fetchall() # closing database self.tempdb.close() # a namedtuple is returned return messages ''' Function to fetch messages (threaded) ''' def getMsgsThreaded(self, zconversation): # progress window progress = QtGui.QProgressDialog("Querying the database ...", "Abort", 0, 0, self) progress.setWindowTitle("Viber Browser ...") progress.setWindowModality(QtCore.Qt.WindowModal) progress.setMinimumDuration(0) progress.setCancelButton(None) progress.show() # ** ZVIBERMESSAGE ** - ZPHONENUMBERINDEX zvibermessage_columns = "ZVIBERMESSAGE.Z_PK, ZVIBERMESSAGE.ZATTACHMENT, ZVIBERMESSAGE.ZLOCATION, ZVIBERMESSAGE.ZPHONENUMINDEX, ZVIBERMESSAGE.ZDATE, ZVIBERMESSAGE.ZSTATEDATE, ZVIBERMESSAGE.ZSTATE, ZVIBERMESSAGE.ZTEXT" conditions = "ZVIBERMESSAGE.ZCONVERSATION=?;" query = "SELECT " + zvibermessage_columns + " FROM ZVIBERMESSAGE WHERE " + conditions # call a thread to query the db showing a progress bar queryTh = ThreadedQuery(self.fname_contacts,query,[zconversation]) queryTh.start() while queryTh.isAlive(): QtGui.QApplication.processEvents() progress.close() messages = queryTh.getResult() # a namedtuple is returned return messages ''' Function to fetch msgs contact info ''' def getMsgContact(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZPHONENUMBERINDEX - ZABCONTACT zphonenumberindex_columns = "ZPHONENUMBERINDEX.ZCANONIZEDPHONENUM" zabcontact_columns = "ZABCONTACT.ZMAINNAME, ZABCONTACT.ZPREFIXNAME" conditions = "ZPHONENUMBERINDEX.Z_PK=? AND ZPHONENUMBERINDEX.ZCONTACT = ZABCONTACT.Z_PK;" query = "SELECT " + zphonenumberindex_columns + ", " + zabcontact_columns + " FROM ZPHONENUMBERINDEX, ZABCONTACT WHERE " + conditions self.tempcur.execute(query, [zpk]) msgcontact = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return msgcontact ''' Function to fetch location info ''' def getLocation(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZVIBERLOCATION table query = "SELECT * FROM ZVIBERLOCATION WHERE Z_PK=?;" self.tempcur.execute(query, [zpk]) location = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return location ''' Function to fetch attachment info ''' def getMediaItem(self, zpk): # opening database try: self.tempdb = sqlite3.connect(self.fname_contacts) except: print("\nUnexpected error: %s"%sys.exc_info()[1]) self.close() # query results are retrieved as a namedtuple # (this step must be before cursor instantiation) self.tempdb.row_factory = namedtuple_factory self.tempcur = self.tempdb.cursor() # ZATTACHMENT table query = "SELECT * FROM ZATTACHMENT WHERE Z_PK=?;" self.tempcur.execute(query, [zpk]) media = self.tempcur.fetchone() # closing database self.tempdb.close() # a namedtuple is returned return media ######################################################################## # SLOTS # ######################################################################## def onChatsClick(self): # disable chats table (to disable click events while processing) self.ui.chatsWidget.setEnabled(False) # retrieving selected row self.ui.chatsWidget.setCurrentCell(self.ui.chatsWidget.currentRow(),0) currentSelectedItem = self.ui.chatsWidget.currentItem() if (currentSelectedItem): pass else: return ###################################################### # MESSAGES SECTION # ###################################################### zpk = int(currentSelectedItem.text()) #msgs = self.getMsgs(zpk) # <--- single thread msgs = self.getMsgsThreaded(zpk) # <--- multithreaded # re-select a visible column to allow the keyboard selection self.ui.chatsWidget.setCurrentCell(self.ui.chatsWidget.currentRow(),1) # erase previous messages and set new table lenght #self.ui.msgsWidget.clearContents() self.ui.msgsWidget.setSortingEnabled(False) self.ui.msgsWidget.setRowCount(len(msgs)) row = 0 for msg in msgs: from_me = False # var to remember from whom is the msg if hasattr(msg, 'Z_PK'): newItem = QtGui.QTableWidgetItem() newItem.setData(QtCore.Qt.DisplayRole,msg.Z_PK) self.ui.msgsWidget.setItem(row, 0, newItem) if hasattr(msg, 'ZPHONENUMINDEX'): newItem = QtGui.QTableWidgetItem() fromstring = "" if msg.ZPHONENUMINDEX is not None: msgcontact = self.getMsgContact(msg.ZPHONENUMINDEX) if msgcontact is not None: if msgcontact.ZPREFIXNAME is not None: fromstring += msgcontact.ZPREFIXNAME + " " if msgcontact.ZMAINNAME is not None: fromstring += msgcontact.ZMAINNAME + " " if msgcontact.ZCANONIZEDPHONENUM is not None: fromstring += msgcontact.ZCANONIZEDPHONENUM else: fromstring = "N/A" else: fromstring = "Me" from_me = True newItem.setData(QtCore.Qt.DisplayRole,fromstring) self.ui.msgsWidget.setItem(row, 1, newItem) if hasattr(msg, 'ZDATE'): newItem = QtGui.QTableWidgetItem(str(self.formatDate(msg.ZDATE))) self.ui.msgsWidget.setItem(row, 2, newItem) if hasattr(msg, 'ZTEXT'): newItem = QtGui.QTableWidgetItem(msg.ZTEXT) self.ui.msgsWidget.setItem(row, 3, newItem) if hasattr(msg, 'ZSTATE'): newItem = QtGui.QTableWidgetItem(msg.ZSTATE) self.ui.msgsWidget.setItem(row, 5, newItem) if hasattr(msg, 'ZSTATEDATE'): newItem = QtGui.QTableWidgetItem(str(self.formatDate(msg.ZSTATEDATE))) self.ui.msgsWidget.setItem(row, 6, newItem) # ATTACHMENTS SECTION if hasattr(msg, 'ZATTACHMENT') and hasattr(msg, 'ZLOCATION'): mediaItem = QtGui.QTableWidgetItem("") msgcontent = "" # case 1: msg contains a location if msg.ZLOCATION is not None: location = self.getLocation(msg.ZLOCATION) msgcontent += "GPS\n" + "lat: " + str(location.ZLATITUDE) + "\nlong: " + str(location.ZLONGITUDE) + "\naddress: " + unicode(location.ZADDRESS) + "\n" # re-set message content (3rd column) newItem = QtGui.QTableWidgetItem(msgcontent) self.ui.msgsWidget.setItem(row, 3, newItem) # compose location thumbnail filename (guess) # example: if LAT = 41.796141 and LONG = 12.481714 # the filename is 41.796141_12.jpg strlat = str(location.ZLATITUDE) strlong = str(location.ZLONGITUDE) strlat_s = strlat.split('.') strlong_s = strlong.split('.') filename = strlat_s[0] + "." + (strlat_s[1])[:6] + "_" + strlong_s[0] + ".jpg" locationlocalfile = filename locationRealFilename = os.path.join(self.backup_path, plugins_utils.realFileName(self.cursor, filename=locationlocalfile, domaintype="AppDomain")) # add a thumbnail to the table view icon = QtGui.QIcon(locationRealFilename) mediaItem.setIcon(icon) # add info for attachment export (ctx menu) mediaItem.setData(QtCore.Qt.UserRole+2, location.ZLATITUDE) mediaItem.setData(QtCore.Qt.UserRole+3, location.ZLONGITUDE) # case 2: msg contains an attachment elif msg.ZATTACHMENT is not None: media = self.getMediaItem(msg.ZATTACHMENT) msgcontent += "ATTACHMENT\n" + "type: " + str(media.ZTYPE) + "\nstate: " + str(media.ZSTATE) + "\nsize: " + str(media.ZFILESIZE) + "B\n" if 'sticker' in media.ZTYPE: msgcontent += "id: " + media.ZID + "\n" # re-set message content (3rd column) newItem = QtGui.QTableWidgetItem(msgcontent) self.ui.msgsWidget.setItem(row, 3, newItem) if media.ZNAME: medialocalfile = media.ZNAME mediaRealFilename = os.path.join(self.backup_path, plugins_utils.realFileName(self.cursor, filename=medialocalfile, domaintype="AppDomain")) # add a thumbnail to the table view icon = QtGui.QIcon(mediaRealFilename) mediaItem.setIcon(icon) # add info for attachment export (ctx menu) mediaItem.setData(QtCore.Qt.UserRole, mediaRealFilename) mediaItem.setData(QtCore.Qt.UserRole+1, medialocalfile) self.ui.msgsWidget.setItem(row, 4, mediaItem) if from_me: for i in range(7): self.ui.msgsWidget.item(row,i).setBackground(QtCore.Qt.green) row = row + 1 self.ui.msgsWidget.setSortingEnabled(True) self.ui.msgsWidget.setIconSize(QtCore.QSize(150,150)) self.ui.msgsWidget.resizeColumnsToContents() self.ui.msgsWidget.setColumnWidth(6, 150) self.ui.msgsWidget.resizeRowsToContents() # signal-slot connection: preserve row height when sorting messages self.ui.msgsWidget.horizontalHeader().sortIndicatorChanged.connect(self.ui.msgsWidget.resizeRowsToContents) # re-enable chats table self.ui.chatsWidget.setEnabled(True) self.ui.chatsWidget.setFocus() ###################################################### # CTX MENU SECTION # ###################################################### def ctxMenuMsgs(self, pos): cell = self.ui.msgsWidget.itemAt(pos) self.link = cell.data(QtCore.Qt.UserRole) self.name = cell.data(QtCore.Qt.UserRole + 1) self.lat = cell.data(QtCore.Qt.UserRole + 2) self.long = cell.data(QtCore.Qt.UserRole + 3) menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVmsgs) menu.addAction(action1) if self.link != None: menu.addSeparator() action1 = QtGui.QAction("Open attachment in standard viewer", self) action1.triggered.connect(self.openWithViewer) menu.addAction(action1) action1 = QtGui.QAction("Export attachment", self) action1.triggered.connect(self.exportSelectedFile) menu.addAction(action1) if (self.lat and self.long) is not None: menu.addSeparator() action1 = QtGui.QAction("Show GPS coordinates on Google Maps", self) action1.triggered.connect(self.openGPSBrowser) menu.addAction(action1) menu.exec_(self.ui.msgsWidget.mapToGlobal(pos)); def ctxMenuContacts(self, pos): cell = self.ui.contactsWidget.itemAt(pos) if cell is not None: self.link = cell.data(QtCore.Qt.UserRole) self.name = cell.data(QtCore.Qt.UserRole + 1) menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVcontacts) menu.addAction(action1) if self.link != None: menu.addSeparator() action1 = QtGui.QAction("Open contact icon in standard viewer", self) action1.triggered.connect(self.openWithViewer) menu.addAction(action1) action1 = QtGui.QAction("Export contact icon", self) action1.triggered.connect(self.exportSelectedFile) menu.addAction(action1) menu.exec_(self.ui.contactsWidget.mapToGlobal(pos)); def ctxMenuCalls(self, pos): menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVcalls) menu.addAction(action1) menu.exec_(self.ui.callsWidget.mapToGlobal(pos)); def ctxMenuChats(self, pos): menu = QtGui.QMenu() action1 = QtGui.QAction("Export table CSV", self) action1.triggered.connect(self.exportCSVchats) menu.addAction(action1) menu.exec_(self.ui.chatsWidget.mapToGlobal(pos)); ##### ATTACHMENTS EXPORT FUNCTIONS ##### def openWithViewer(self): if sys.platform.startswith('linux'): subprocess.call(["xdg-open", self.link]) else: os.startfile(self.link) def exportSelectedFile(self): filename = QtGui.QFileDialog.getSaveFileName(self, "Export attachment", self.name) filename = filename[0] if (len(filename) == 0): return try: shutil.copy(self.link, filename) QtGui.QMessageBox.about(self, "Confirm", "Attachment saved as %s."%filename) except: QtGui.QMessageBox.about(self, "Error", "Error while saving attachment") def openGPSBrowser(self): coordinatesURL = "https://maps.google.com/?q=" + str(self.lat) + "," + str(self.long) webbrowser.open(coordinatesURL) ##### TABLES EXPORT FUNCTIONS ##### def exportCSVcontacts(self): self.exportCSVtable(self.ui.contactsWidget) def exportCSVcalls(self): self.exportCSVtable(self.ui.callsWidget) def exportCSVchats(self): self.exportCSVtable(self.ui.chatsWidget) def exportCSVmsgs(self): self.exportCSVtable(self.ui.msgsWidget) def exportCSVtable(self, table): filename = QtGui.QFileDialog.getSaveFileName(self, "Export table", "table", ".csv") filename = filename[0] if (len(filename) == 0): return f = open(filename, 'w') # header tablerow='"' for c in range(table.columnCount()): hitem = table.horizontalHeaderItem(c) if hitem is not None: tablerow += unicode(hitem.text()).encode('utf8') tablerow += '","' tablerow=tablerow[:-2]+"\n" f.write(tablerow) # content tablerow='"' for r in range(table.rowCount()): for c in range(table.columnCount()): item = table.item(r,c) if item is not None: tablerow += unicode(item.text().replace('\n',' ')).encode('utf8') tablerow += '","' tablerow = tablerow[:-2] + "\n" f.write(tablerow) tablerow='"' f.close()