From 87495d957d57a6f43ba1731a37e3512754844ede Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 9 Jan 2023 20:29:16 +0200 Subject: [PATCH 01/12] Tweak program version comparison. --- src/skyperious/support.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/skyperious/support.py b/src/skyperious/support.py index 8874d27..7c5b084 100644 --- a/src/skyperious/support.py +++ b/src/skyperious/support.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 16.04.2013 -@modified 27.03.2022 +@modified 09.01.2023 ------------------------------------------------------------------------------ """ import datetime @@ -238,11 +238,11 @@ def get_install_type(): def canonic_version(v): """Returns a numeric version representation: "1.3.2a" to 10301,99885.""" - nums = [int(re.sub("[^\\d]", "", x)) for x in v.split(".")][::-1] + nums = [int(re.sub("[^\\d].*$", "", x)) for x in v.split(".") if re.match("^\\d", x)][::-1] nums[0:0] = [0] * (3 - len(nums)) # Zero-pad if version like 1.4 or just 2 # Like 1.4a: subtract 1 and add fractions to last number to make < 1.4 - if re.findall("\\d+([\\D]+)$", v): - ords = [ord(x) for x in re.findall("\\d+([\\D]+)$", v)[0]] + if re.findall("[.\\d]([^.\\d][^.]+)$", v): + ords = [ord(x) for x in re.findall("[.\\d]([^.\\d][^.]+)$", v)[0]] nums[0] += sum(x / (65536. ** (i + 1)) for i, x in enumerate(ords)) - 1 return sum((x * 100 ** i) for i, x in enumerate(nums)) From 99e3ba999aab911d859cb9695a230bf76aedd7de Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 9 Jan 2023 20:32:11 +0200 Subject: [PATCH 02/12] Add config option to disable automatic update checks and downloads. --- CHANGELOG.md | 7 ++++++- src/skyperious/conf.py | 11 +++++++---- src/skyperious/gui.py | 14 ++++++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd2f51..5df788c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ CHANGELOG ========= -5.5, 2022-12-13 +5.4.2, 2023-01-09 +--------------- +- add config option to disable automatic update checks and downloads + + +5.4.1, 2023-01-02 --------------- - fix --version flag being ignored diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index aec8d32..bd3aca2 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 13.12.2022 +@modified 09.01.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -24,8 +24,8 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.5.dev0" -VersionDate = "13.12.2022" +Version = "5.4.2dev0" +VersionDate = "09.01.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable @@ -149,9 +149,12 @@ """Whether the program tray icon is used.""" TrayIconEnabled = True -"""Whether the program checks for updates every UpdateCheckInterval.""" +"""Whether the program checks for updates every UpdateCheckInterval if UpdateCheckEnabled.""" UpdateCheckAutomatic = True +"""Whether the program supports automatic update check and download.""" +UpdateCheckEnabled = True + """Whether the program has been minimized and hidden.""" WindowIconized = False diff --git a/src/skyperious/gui.py b/src/skyperious/gui.py index 2556ce4..4250192 100644 --- a/src/skyperious/gui.py +++ b/src/skyperious/gui.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 13.10.2022 +@modified 09.01.2023 ------------------------------------------------------------------------------ """ import ast @@ -525,7 +525,8 @@ def create_menu(self): id2=wx.ID_FILE1 + conf.MaxRecentFiles) if self.trayicon.IsAvailable(): menu_tray.Check(conf.TrayIconEnabled) - menu_autoupdate_check.Check(conf.UpdateCheckAutomatic) + menu_autoupdate_check.Check(conf.UpdateCheckEnabled and conf.UpdateCheckAutomatic) + menu_autoupdate_check.Enable(conf.UpdateCheckEnabled) self.Bind(wx.EVT_MENU, self.on_new_blank, menu_new_blank) if menu_new_live: @@ -553,7 +554,7 @@ def update_check(self): from last check has passed, and opens a dialog for upgrading if new version available. Schedules a new check on due date. """ - if self or not conf.UpdateCheckAutomatic: return + if not self or not conf.UpdateCheckEnabled or not conf.UpdateCheckAutomatic: return interval = datetime.timedelta(days=conf.UpdateCheckInterval) due_date = datetime.datetime.now() - interval @@ -930,7 +931,12 @@ def on_check_update_callback(self, check_result, full_response=True): MAX = 1000 changes = changes[:MAX] + ".." if len(changes) > MAX else changes guibase.status("New %s version %s available.", conf.Title, version) - if wx.OK == wx.MessageBox( + if not conf.UpdateCheckEnabled: wx.MessageBox( + "Newer version (%s) available. You are currently on version %s.%s" % + (version, conf.Version, "\n\n%s\n" % changes), + "Update information", wx.OK | wx.ICON_INFORMATION + ) + elif wx.OK == wx.MessageBox( "Newer version (%s) available. You are currently on " "version %s.%s\nDownload and install %s %s?" % (version, conf.Version, "\n\n%s\n" % changes, From 06493db1c855c1eb5de06e20d026d8f970074dbb Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Thu, 1 Jun 2023 19:13:22 +0300 Subject: [PATCH 03/12] Avoid needless error in log if chat has no picture. --- src/skyperious/conf.py | 6 +++--- src/skyperious/gui.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index bd3aca2..b023f57 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 09.01.2023 +@modified 01.06.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -24,8 +24,8 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.4.2dev0" -VersionDate = "09.01.2023" +Version = "5.4.2dev1" +VersionDate = "01.06.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable diff --git a/src/skyperious/gui.py b/src/skyperious/gui.py index 4250192..a02f2f5 100644 --- a/src/skyperious/gui.py +++ b/src/skyperious/gui.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 09.01.2023 +@modified 01.06.2023 ------------------------------------------------------------------------------ """ import ast @@ -6447,7 +6447,7 @@ def populate_chat_statistics(self): img, imgkey = self.imagecache.get(("chat", self.chat["id"])), ("chat", self.chat["id"]) try: raw = skypedata.fix_image_raw(pic) if pic and not img else None - img = img or wx.Image(io.BytesIO(raw.encode("latin1"))) + img = img or (wx.Image(io.BytesIO(raw.encode("latin1"))) if raw else None) except Exception: logger.exception("Error loading image for %s.", self.chat["title_long_lc"]) if img: From 8fb55f782e8984130cee62a99fd9ccc5480e9131 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Thu, 1 Jun 2023 19:23:47 +0300 Subject: [PATCH 04/12] Fix importing latest Skype export archives (issue #119). --- CHANGELOG.md | 3 ++- src/skyperious/conf.py | 2 +- src/skyperious/live.py | 16 ++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5df788c..1f43aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ CHANGELOG ========= -5.4.2, 2023-01-09 +5.4.2, 2023-06-01 --------------- +- fix importing latest Skype export archives (issue #119) - add config option to disable automatic update checks and downloads diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index b023f57..1f2c5cf 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -24,7 +24,7 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.4.2dev1" +Version = "5.4.2dev2" VersionDate = "01.06.2023" if getattr(sys, "frozen", False): diff --git a/src/skyperious/live.py b/src/skyperious/live.py index bdbc277..07028fe 100644 --- a/src/skyperious/live.py +++ b/src/skyperious/live.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 08.07.2020 -@modified 12.10.2022 +@modified 01.06.2023 ------------------------------------------------------------------------------ """ import collections @@ -1133,6 +1133,7 @@ def export_read(self, progress=None): def export_parse(self, f, progress=None): """Parses JSON data from file pointer and inserts to database.""" parser = ijson.parse(f) + PREFIX_RGX = re.compile(r"^\d+\:") # Matching and stripping numeric prefix self.get_tables() self.table_objects.setdefault("contacts", {}) @@ -1217,8 +1218,9 @@ def export_parse(self, f, progress=None): skip_chat = value.startswith(skypedata.ID_PREFIX_SPECIAL) chat["identity"] = value chat["type"] = skypedata.CHATS_TYPE_GROUP - if value.startswith(skypedata.ID_PREFIX_SINGLE): - chat["identity"] = value[len(skypedata.ID_PREFIX_SINGLE):] + if not value.startswith(skypedata.ID_PREFIX_GROUP): + if not value.startswith(skypedata.ID_PREFIX_BOT): + chat["identity"] = PREFIX_RGX.sub("", value) chat["type"] = skypedata.CHATS_TYPE_SINGLE except Exception: logger.warning("Error parsing chat identity %r for %s.", value, chat, exc_info=True) @@ -1235,12 +1237,18 @@ def export_parse(self, f, progress=None): elif "conversations.item.threadProperties.members" == prefix: if skip_chat: continue # while True try: - for identity in map(id_to_identity, json.loads(value)): + if value is not None or skypedata.CHATS_TYPE_GROUP == chat["type"]: + identities = map(id_to_identity, json.loads(value)) + else: identities = [self.id, chat["identity"]] + for identity in identities: if not identity: continue # for identity if identity not in self.table_objects["contacts"] and identity != self.id: contact = dict(skypename=identity, is_permanent=1) if identity.startswith(skypedata.ID_PREFIX_BOT): contact["type"] = skypedata.CONTACT_TYPE_NORMAL + if skypedata.CHATS_TYPE_GROUP != chat["type"] \ + and identity == chat["identity"] and chat.get("displayname"): + contact["displayname"] = chat["displayname"] contact["id"] = self.insert_row("contacts", contact) self.table_objects["contacts"][identity] = contact p = dict(is_permanent=1, convo_id=chat["id"], identity=identity) From 6e986d34a2022ab572a65f38779f95d017cd8fb0 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Sat, 15 Jul 2023 21:15:39 +0300 Subject: [PATCH 05/12] Fix Bing-bot not being synced properly (issue #117). --- CHANGELOG.md | 11 ++++++----- src/skyperious/conf.py | 6 +++--- src/skyperious/live.py | 26 ++++++++++++++++---------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f43aac..8b1e1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ CHANGELOG ========= -5.4.2, 2023-06-01 ---------------- +5.4.2, 2023-07-15 +----------------- +- fix Bing-bot not being synced properly (issue #117) - fix importing latest Skype export archives (issue #119) - add config option to disable automatic update checks and downloads 5.4.1, 2023-01-02 ---------------- +----------------- - fix --version flag being ignored @@ -83,7 +84,7 @@ CHANGELOG 4.8.1, 2021-08-05 ---------------- +----------------- - fixed audio/video messages imported from live as simple file transfers (issue #102) @@ -219,7 +220,7 @@ CHANGELOG 3.5.1c, 2015-12-21 ---------------- +------------------ - added support for updated Skype chat structure (issue #40) - added "Remove by type" button to start page (issue #47) - fixed escaping bolded words in search chat title and participants link diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index 1f2c5cf..6fb0b52 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 01.06.2023 +@modified 15.07.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -24,8 +24,8 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.4.2dev2" -VersionDate = "01.06.2023" +Version = "5.4.2dev3" +VersionDate = "15.07.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable diff --git a/src/skyperious/live.py b/src/skyperious/live.py index 07028fe..3a78e44 100644 --- a/src/skyperious/live.py +++ b/src/skyperious/live.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 08.07.2020 -@modified 01.06.2023 +@modified 15.07.2023 ------------------------------------------------------------------------------ """ import collections @@ -272,7 +272,7 @@ def save(self, table, item, parent=None): if "contacts" == table and not dbitem0 \ and not (identity or "").startswith(skypedata.ID_PREFIX_BOT): - # Bot accounts from live are without bot prefix + # Bot accounts from live can be without bot prefix dbitem0 = self.cache[table].get(skypedata.ID_PREFIX_BOT + identity) if dbitem0: identity = skypedata.ID_PREFIX_BOT + identity @@ -422,7 +422,7 @@ def insert_participants(self, chat, row, row0): uidentity = user.id if user else uid # Keep bot prefixes on bot accounts if uidentity != self.skype.userId and not uidentity.startswith(skypedata.ID_PREFIX_BOT) \ - and (isinstance(user, skpy.SkypeBotUser) or chat.id.startswith(skypedata.ID_PREFIX_BOT)): + and (is_bot(user) or chat.id.startswith(skypedata.ID_PREFIX_BOT)): uidentity = skypedata.ID_PREFIX_BOT + uidentity p = dict(is_permanent=1, convo_id=row["id"], identity=uidentity) if isinstance(chat, skpy.SkypeGroupChat) and (user.id if user else uid) == chat.creatorId: @@ -570,8 +570,7 @@ def convert(self, table, item, parent=None): else: result = None elif "contacts" == table: - result.update(type=skypedata.CONTACT_TYPE_BOT - if isinstance(item, skpy.SkypeBotUser) + result.update(type=skypedata.CONTACT_TYPE_BOT if is_bot(item) else skypedata.CONTACT_TYPE_NORMAL, isblocked=getattr(item, "blocked", False), isauthorized=getattr(item, "authorised", False)) @@ -583,8 +582,7 @@ def convert(self, table, item, parent=None): result.update(mood_text=item.mood.plain, rich_mood_text=item.mood.rich) if item.avatar: result.update(avatar_url=item.avatar) - if isinstance(item, skpy.SkypeBotUser) \ - and not item.id.startswith(skypedata.ID_PREFIX_BOT): + if is_bot(item) and not item.id.startswith(skypedata.ID_PREFIX_BOT): result.update(skypename=skypedata.ID_PREFIX_BOT + item.id) elif "messages" == table: @@ -598,7 +596,7 @@ def convert(self, table, item, parent=None): if parent: identity = parent.id if isinstance(parent, skpy.SkypeGroupChat) \ - or isinstance(parent.user, skpy.SkypeBotUser) \ + or is_bot(parent.user) \ or result["author"].startswith(skypedata.ID_PREFIX_BOT) else parent.userId chat = self.cache["chats"].get(identity) if not chat: @@ -849,8 +847,7 @@ def process_chat(self, chat, full, chats_processed, chats_updated, chats_complet @return whether further progress should continue """ result = True - cidentity = chat.id if isinstance(chat, skpy.SkypeGroupChat) \ - or isinstance(chat.user, skpy.SkypeBotUser) \ + cidentity = chat.id if isinstance(chat, skpy.SkypeGroupChat) or is_bot(chat.user) \ or chat.id.startswith(skypedata.ID_PREFIX_BOT) else chat.userId if chat.id.startswith(skypedata.ID_PREFIX_SPECIAL): # Skip specials like "48:calllogs" return result @@ -1605,6 +1602,15 @@ def identity_to_id(identity): return result +def is_bot(user): + """Returns whether the given skpy.SkypeUser is a bot.""" + return isinstance(user, skpy.SkypeBotUser) \ + or (isinstance(user, skpy.SkypeUser) and isinstance(getattr(user, "raw", None), dict) + and isinstance(user.raw.get("person_id"), six.string_types) + and user.raw["person_id"].startswith(skypedata.ID_PREFIX_BOT) + ) # Workaround for skpy bug of presenting bots in some contexts as plain contacts + + def make_db_path(username): """Returns the default database path for username.""" base = util.safe_filename(username) From 1cca4633e8279a6cc5f6b67181f8d03c135a79fc Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 17 Jul 2023 17:44:17 +0300 Subject: [PATCH 06/12] Fix older bot entries in database upon sync. --- src/skyperious/conf.py | 6 +++--- src/skyperious/live.py | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index 6fb0b52..560eab2 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 15.07.2023 +@modified 18.07.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -24,8 +24,8 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.4.2dev3" -VersionDate = "15.07.2023" +Version = "5.4.2dev4" +VersionDate = "18.07.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable diff --git a/src/skyperious/live.py b/src/skyperious/live.py index 3a78e44..c032dfe 100644 --- a/src/skyperious/live.py +++ b/src/skyperious/live.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 08.07.2020 -@modified 15.07.2023 +@modified 18.07.2023 ------------------------------------------------------------------------------ """ import collections @@ -279,6 +279,11 @@ def save(self, table, item, parent=None): dbitem["skypename"] = dbitem1["skypename"] = identity dbitem["type"] = dbitem1["type"] = skypedata.CONTACT_TYPE_BOT + if "contacts" == table and not dbitem0 \ + and (identity or "").startswith(skypedata.ID_PREFIX_BOT): + # Fix bot entries in database not having bot prefix + dbitem0 = self.cache[table].get(identity[len(skypedata.ID_PREFIX_BOT):]) + if "messages" == table and dbitem.get("remote_id") \ and not isinstance(item, (skpy.SkypeCallMsg, skpy.SkypeMemberMsg, skpy.msg.SkypePropertyMsg)): # Look up message by remote_id instead, to detect edited messages @@ -344,6 +349,17 @@ def save(self, table, item, parent=None): for k, v in dbitem0.items(): if k not in dbitem: dbitem[k] = v dbitem["__updated__"] = True + + if "contacts" == table and identity.startswith(skypedata.ID_PREFIX_BOT) \ + and not dbitem0["skypename"].startswith(skypedata.ID_PREFIX_BOT): + # Fix bot entries in database not having bot prefix + self.db.execute("UPDATE participants SET identity = :id WHERE identity = :id0", + {"id": identity, "id0": dbitem0["skypename"]}) + self.db.execute("UPDATE transfers SET partner_handle = :id WHERE partner_handle = :id0", + {"id": identity, "id0": dbitem0["skypename"]}) + self.db.execute("UPDATE messages SET author = :id WHERE author = :id0", + {"id": identity, "id0": dbitem0["skypename"]}) + dbitem0["skypename"] = identity else: dbitem["id"] = self.db.insert_row(dbtable, dbitem, log=False) dbitem["__inserted__"] = True @@ -994,12 +1010,17 @@ def download_content(self, url, category=None): def get_contact(self, identity): """Returns cached contact, if any.""" - return self.cache["contacts"].get(identity) if identity else None + result = self.cache["contacts"].get(identity) if identity else None + if not result and identity and not identity.startswith(skypedata.ID_PREFIX_BOT): + result = self.cache["contacts"].get(skypedata.ID_PREFIX_BOT + identity) + return result def get_contact_name(self, identity): """Returns contact displayname or fullname or identity.""" result, contact = None, self.get_contact(identity) + if not contact: contact = self.get_contact(identity) + if contact: result = contact.get("displayname") or contact.get("fullname") result = result or identity From d740a5393a92053e0dc890c9215b60657d021908 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 17 Jul 2023 17:56:07 +0300 Subject: [PATCH 07/12] Handle potential error in update check. --- CHANGELOG.md | 4 ++-- src/skyperious/conf.py | 2 +- src/skyperious/gui.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1e1bf..15f1e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ CHANGELOG ========= -5.4.2, 2023-07-15 ------------------ +5.5, 2023-07-17 +--------------- - fix Bing-bot not being synced properly (issue #117) - fix importing latest Skype export archives (issue #119) - add config option to disable automatic update checks and downloads diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index 560eab2..699e6e6 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -24,7 +24,7 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.4.2dev4" +Version = "5.5dev5" VersionDate = "18.07.2023" if getattr(sys, "frozen", False): diff --git a/src/skyperious/gui.py b/src/skyperious/gui.py index a02f2f5..d6f3c04 100644 --- a/src/skyperious/gui.py +++ b/src/skyperious/gui.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 01.06.2023 +@modified 17.07.2023 ------------------------------------------------------------------------------ """ import ast @@ -559,10 +559,10 @@ def update_check(self): interval = datetime.timedelta(days=conf.UpdateCheckInterval) due_date = datetime.datetime.now() - interval if not (conf.WindowIconized or support.update_window) \ - and conf.LastUpdateCheck < due_date.strftime("%Y%m%d"): + and (not conf.LastUpdateCheck or conf.LastUpdateCheck < due_date.strftime("%Y%m%d")): callback = lambda resp: self.on_check_update_callback(resp, False) support.check_newest_version(callback) - elif not support.update_window: + elif not support.update_window and conf.LastUpdateCheck: try: dt = datetime.datetime.strptime(conf.LastUpdateCheck, "%Y%m%d") interval = (dt + interval) - datetime.datetime.now() From 011d6c04cc0f328e57920619937edccd31366911 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 17 Jul 2023 18:19:17 +0300 Subject: [PATCH 08/12] Support Pillow v10. --- src/skyperious/conf.py | 4 ++-- src/skyperious/export.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index 699e6e6..7ea13cd 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 18.07.2023 +@modified 17.07.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -25,7 +25,7 @@ """Program title, version number and version date.""" Title = "Skyperious" Version = "5.5dev5" -VersionDate = "18.07.2023" +VersionDate = "17.07.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable diff --git a/src/skyperious/export.py b/src/skyperious/export.py index ccba7c2..1e2a92a 100644 --- a/src/skyperious/export.py +++ b/src/skyperious/export.py @@ -8,7 +8,7 @@ @author Erki Suurjaak @created 13.01.2012 -@modified 20.09.2022 +@modified 17.07.2023 ------------------------------------------------------------------------------ """ import codecs @@ -558,9 +558,9 @@ def __init__(self, filename, sheetname=None, autowrap=()): # For calculating column widths self._fonts = collections.defaultdict(lambda: FONT_XLSX) self._fonts["bold"] = FONT_XLSX_BOLD - unit_width_default = self._fonts[None].getsize("0")[0] + unit_width_default = get_extent(self._fonts[None], "0")[0] self._unit_widths = collections.defaultdict(lambda: unit_width_default) - self._unit_widths["bold"] = self._fonts["bold"].getsize("0")[0] + self._unit_widths["bold"] = get_extent(self._fonts["bold"], "0")[0] if sheetname: # Create default sheet self.add_sheet(sheetname) @@ -645,7 +645,7 @@ def writerow(self, values, style=None, merge_cols=0, autowidth=True): if isinstance(v, six.text_type) else v.strftime("%Y-%m-%d %H:%M") if isinstance(v, datetime.datetime) else v if isinstance(v, six.string_types) else str(v)) - pixels = max(self._fonts[fmt_name].getsize(x)[0] + pixels = max(get_extent(self._fonts[fmt_name], x)[0] for x in strval.split("\n")) width = float(pixels) / self._unit_widths[fmt_name] + 1 if not merge_cols and width > self._col_widths[self._sheet.name][c]: @@ -667,3 +667,9 @@ def close(self): (conf.Title, datetime.datetime.now().strftime("%d.%m.%Y %H:%M")), "author": "%s %s" % (conf.Title, conf.Version)}) self._workbook.close() + + +def get_extent(font, text): + """Returns (width, height) for specified font and text.""" + if hasattr(font, "getsize"): return font.getsize(text) # < PIL v10 + if hasattr(font, "getbbox"): return font.getbbox(text)[2:] # >= PIL v10 From 4b7caa93ca3779d6dd5b32d00438da814cd75368 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Mon, 17 Jul 2023 19:50:43 +0300 Subject: [PATCH 09/12] Upgrade all dependency libraries in compiled binaries. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f1e74..2846b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG - fix Bing-bot not being synced properly (issue #117) - fix importing latest Skype export archives (issue #119) - add config option to disable automatic update checks and downloads +- upgrade all dependency libraries in compiled binaries 5.4.1, 2023-01-02 From 14034666fbcade77e0bf2c4950d49248cee4940b Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Tue, 18 Jul 2023 10:12:13 +0300 Subject: [PATCH 10/12] Detect shortcut keys from toolbar labels as well. --- src/skyperious/conf.py | 6 +- src/skyperious/lib/wx_accel.py | 112 ++++++++++++++++----------------- 2 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index 7ea13cd..c630376 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -10,7 +10,7 @@ @author Erki Suurjaak @created 26.11.2011 -@modified 17.07.2023 +@modified 18.07.2023 ------------------------------------------------------------------------------ """ try: from ConfigParser import RawConfigParser # Py2 @@ -24,8 +24,8 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.5dev5" -VersionDate = "17.07.2023" +Version = "5.5.dev6" +VersionDate = "18.07.2023" if getattr(sys, "frozen", False): # Running as a pyinstaller executable diff --git a/src/skyperious/lib/wx_accel.py b/src/skyperious/lib/wx_accel.py index 450e72b..9be6beb 100644 --- a/src/skyperious/lib/wx_accel.py +++ b/src/skyperious/lib/wx_accel.py @@ -12,8 +12,9 @@ - wx.TextCtrl control focused, all text selected - wx.RadioButton control focused, value selected - wx.Control control focused -- wx.ToolBar tool event is called, if the tool shorthelp includes a - parseable shortcut key like (Alt-S) +- wx.ToolBar tool event is called, if the tool label contains shortcut + or if the tool shorthelp includes a parseable shortcut key + like "(Alt-S)" - wx.ToggleButton ToggleButton handler called Uses primitive heuristic analysis to detect connected label-control pairs: @@ -30,7 +31,7 @@ @author Erki Suurjaak @created 19.11.2011 -@modified 26.03.2022 +@modified 18.07.2023 ------------------------------------------------------------------------------ """ import functools @@ -87,6 +88,42 @@ def UpdateAccelerators(self, use_heuristics=True): +def parse_label(text): + """Returns first suitable ampersanded shortcut key from text, in lowercase, or None.""" + for part in filter(len, text.split("&")[1:]): + key = part[0].lower() # Find usable ampersand, preceding a valid character + if key > " ": return key + return None + + +def parse_shortcuts(ctrl): + """ + Parses the shortcut keys from the control label, if any. + If wx.ToolBar, parses tool labels, and shorthelps for explicit shortcut texts. + + @return {key: control or toolbar tool ID} + """ + result = {} + if isinstance(ctrl, wx.ToolBar): + for i in range(ctrl.GetToolsCount()): + tool = ctrl.GetToolByPos(i) + if not tool: continue # for i + key = parse_label(tool.Label) + if key: + result.setdefault(key, tool.Id) + if DEBUG: print("Parsed '%s' in tool label '%s'." % (key, tool.Label)) + parts = re.split("\\(Alt-(.)\\)", tool.ShortHelp, maxsplit=1) + if len(parts) > 1: + result.setdefault(parts[1].lower(), tool.Id) + # wx.TextCtrl.Label is the same as its value, so must not use that + elif hasattr(ctrl, "Label") and not isinstance(ctrl, wx.TextCtrl): + key = parse_label(ctrl.Label) + if key: + result[key] = ctrl.Id + if DEBUG: print("Parsed '%s' in label '%s'." % (key, ctrl.Label)) + return result + + def collect_shortcuts(control, use_heuristics=True): """ Returns a map of detected shortcut keys and target controls under the @@ -105,35 +142,6 @@ def collect_shortcuts(control, use_heuristics=True): nameds = {} # collected controls with Name {name: control, } statics = {} # collected StaticTexts with a shortcut {control: char, } - def parse_shortcuts(ctrl): - """ - Parses the shortcut keys from the control label, if any. - - @return [keys] - """ - result = [] - # wx.TextCtrl.Label is the same as its value, so must not use that - if isinstance(ctrl, wx.ToolBar): - for i in range(ctrl.GetToolsCount()): - tool = ctrl.GetToolByPos(i) - if not tool: continue # for i - text = ctrl.GetToolShortHelp(tool.GetId()) - parts = re.split("\\(Alt-(.)\\)", text, maxsplit=1) - if len(parts) > 1: - result.append(parts[1].lower()) - elif hasattr(ctrl, "Label") and not isinstance(ctrl, wx.TextCtrl): - for part in filter(len, ctrl.Label.split("&")[1:]): - # Labels have potentially multiple ampersands - find one that - # is usable (preceding a valid character. 32 and lower are - # spaces, punctuation, control characters, etc). - key = part[0].lower() - if ord(key) > 32: - result.append(key) - if DEBUG and key: - print("Parsed '%s' in label '%s'." % (key, ctrl.Label)) - break # for part - return result - def collect_recurse(ctrl, result, nameds, statics): """ @@ -147,8 +155,7 @@ def collect_recurse(ctrl, result, nameds, statics): for i in range(len(children)): collect_recurse(children[i], result, nameds, statics) - keys = parse_shortcuts(ctrl) - for key in keys: + for key in parse_shortcuts(ctrl): if isinstance(ctrl, wx.StaticText): statics[ctrl] = key else: @@ -230,10 +237,9 @@ def collect_recurse(ctrl, result, nameds, statics): if not target: continue # for name, ctrl - key = (parse_shortcuts(ctrl) + [""]).pop(0) + key = next(iter(parse_shortcuts(ctrl)), "") if DEBUG: - print("Name %s matches potential %s, key=%s." % ( - name, basename, key)) + print("Name %s matches potential %s, key=%s." % (name, basename, key)) if target not in result_values: if target not in result.get(key, []): result.setdefault(key, []).append((target, ctrl)) @@ -247,7 +253,6 @@ def collect_recurse(ctrl, result, nameds, statics): return result - def accelerate(window, use_heuristics=True, skipclicklabels=None, accelerators=None): """ Assigns global keyboard shortcuts to all controls under the specified @@ -280,8 +285,7 @@ def eventhandler(targets, key, shortcut_event): @param shortcut_event menu event generated by the accelerator table """ if DEBUG: - print("Handling target %s" % - [(type(t), t.Id, t.Label) for t in targets]) + print("Handling target %s" % [(type(t), t.Id, t.Label) for t in targets]) event, target = None, None for target in targets: if not target or not isinstance(target, wx.Control) \ @@ -308,23 +312,14 @@ def eventhandler(targets, key, shortcut_event): else: target.Value = not target.Value target.SetFocus() elif isinstance(target, wx.ToolBar): - # Toolbar shortcuts are defined in their shorthelp texts - toolsmap, tb = dict(), target - for i in range(tb.GetToolsCount() + 1): - try: - tool = tb.FindToolForPosition(i * tb.ToolSize[0], 0) - toolsmap[repr(tool)] = tool - except Exception: pass # FindTool not implemented in GTK - for tool in filter(bool, toolsmap.values()): - id = tool.GetId() - text = tb.GetToolShortHelp(id) - parts = re.split("\\(Alt-(%s)\\)" % key, text, - maxsplit=1, flags=re.IGNORECASE) - if len(parts) > 1: - event = wx.CommandEvent(wx.EVT_TOOL.typeId, id) - event.SetEventObject(target) - target.ToggleTool(id, not target.GetToolState(id)) - break # for tool + # Toolbar shortcuts are defined in tool labels and shorthelp texts + id_tool = parse_shortcuts(target).get(key) + if id_tool: + event = wx.CommandEvent(wx.EVT_TOOL.typeId, id_tool) + event.SetEventObject(target) + event.SetInt(not target.GetToolState(id_tool)) + target.ToggleTool(id_tool, not target.GetToolState(id_tool)) + break # for tool else: target.SetFocus() if isinstance(target, wx.TextCtrl): @@ -356,8 +351,7 @@ def eventhandler(targets, key, shortcut_event): skipclicklabels.add(label) if not key: continue # for key, targets ctrls = [t[0] for t in targets] - if DEBUG: print("Binding %s to targets %s." % - (key, [type(t) for t in ctrls])) + if DEBUG: print("Binding %s to targets %s." % (key, [type(t) for t in ctrls])) menu_item = dummy_menu.Append(wx.ID_ANY, "&%s" % key) window.Bind(wx.EVT_MENU, functools.partial(eventhandler, ctrls, key), menu_item) From 590369ae55d6433ef8f0050698b67c0ca8622dcf Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Tue, 18 Jul 2023 10:21:12 +0300 Subject: [PATCH 11/12] Remove outdated instruction on exporting chat history from Skype client. --- README.md | 5 ++--- build/README for Windows.txt | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1fc4802..534fab6 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,8 @@ scratch, by downloading all available history. Skyperious can read chat history archives exported from Skype, and merge their contents into an existing database, or create a new database from their contents. -(Skype export is available in Skype client v8.5 under - Settings -> Messaging -> Export chat history from Skype 7.x; - and in Skype web interface under My account -> Export files and chat history. +(Skype export is available in Skype web interface under + My account -> Export files and chat history. The result is a `.tar` archive containing a `messages.json` file.) HTML export can download shared photos and embed them in the resulting HTML, diff --git a/build/README for Windows.txt b/build/README for Windows.txt index 5b6fc30..93e0dfe 100644 --- a/build/README for Windows.txt +++ b/build/README for Windows.txt @@ -68,9 +68,8 @@ scratch, by downloading all available history. Skyperious can read chat history archives exported from Skype, and merge their contents into an existing database, or create a new database from their contents. -(Skype export is available in Skype client v8.5 under - Settings -> Messaging -> Export chat history from Skype 7.x; - and in Skype web interface under My account -> Export files and chat history. +(Skype export is available in Skype web interface under + My account -> Export files and chat history. The result is a .tar archive containing a messages.json file.) HTML export can download shared photos and embed them in the resulting HTML, From d8e87095a8adb709532a67ffa171909111eed869 Mon Sep 17 00:00:00 2001 From: Erki Suurjaak Date: Tue, 18 Jul 2023 15:48:05 +0300 Subject: [PATCH 12/12] Avoid warnings from BeautifulSoup upon live sync. --- src/skyperious/conf.py | 2 +- src/skyperious/live.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/skyperious/conf.py b/src/skyperious/conf.py index c630376..106e878 100644 --- a/src/skyperious/conf.py +++ b/src/skyperious/conf.py @@ -24,7 +24,7 @@ """Program title, version number and version date.""" Title = "Skyperious" -Version = "5.5.dev6" +Version = "5.5.dev7" VersionDate = "18.07.2023" if getattr(sys, "frozen", False): diff --git a/src/skyperious/live.py b/src/skyperious/live.py index c032dfe..d29625d 100644 --- a/src/skyperious/live.py +++ b/src/skyperious/live.py @@ -45,6 +45,7 @@ # Avoid BeautifulSoup popup warnings like MarkupResemblesLocatorWarning warnings.filterwarnings("ignore", category=UserWarning, module="bs4") +warnings.filterwarnings("ignore", category=UserWarning, module="skpy") class SkypeLogin(object):