-
Notifications
You must be signed in to change notification settings - Fork 0
/
ProfileWindow.py
963 lines (832 loc) · 38.1 KB
/
ProfileWindow.py
1
"""Macstodon - a Mastodon client for classic Mac OSMIT LicenseCopyright (c) 2022-2024 Scott Small and ContributorsPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associateddocumentation files (the "Software"), to deal in the Software without restriction, including without limitation therights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permitpersons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of theSoftware.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THEWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS ORCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OROTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""# ############### Python Imports # ##############import EasyDialogsimport icimport Listsimport Qdimport reimport stringimport timeimport urllibimport urlparseimport W# ########### My Imports# ##########from MacstodonConstants import VERSIONfrom MacstodonHelpers import attachmentsDialog, cleanUpUnicode, dprint, handleRequest, ImageWidget, \ LinkExtractor, linksDialog, okCancelDialog, okDialog, ProfileBanner, ProfilePanel, speak, \ TitledEditText, TimelineList# ############ Application# ###########class ProfileWindow(W.Window): def __init__(self, account): """ Initializes the ProfileWindow class. """ # Set window size. Default to 400x400 which fits nicely in a 640x480 display. # However if you're on a compact Mac that is 512x342, we need to make it smaller. screenbounds = Qd.qd.screenBits.bounds if screenbounds[3] <= 400: bounds = (0, 20, 400, 342) else: bounds = (400, 400) self.account = account self.acct_name = None self.domain_name = None self.defaulttext = "Click on a toot or notification in one of the above lists..." self.timeline = [] W.Window.__init__(self, bounds, "Macstodon %s - Profile" % VERSION, minsize=(400, 342)) self.setupwidgets() def setupwidgets(self): """ Defines the Profile window. """ prefs = self.parent.getprefs() self.panes = W.HorizontalPanes((8, 8, -8, -20), (0.35, 0.45, 0.2)) self.panes.avgroup = W.Group(None) self.panes.avgroup.banner = ProfileBanner((0, 0, 0, 0), drop_shadow=prefs.show_banners) self.panes.avgroup.avatar = ImageWidget((8, 8, 48, 48)) self.panes.avgroup.note = W.TextBox((-106, 10, 96, 48), "") self.panes.avgroup.note.show(0) self.panes.avgroup.followBtn = W.Button((8, -20, 64, 16), "Follow", callback=self.followCallback) self.panes.avgroup.actionsmenu = W.PopupWidget((80, -20, 16, 16)) self.panes.tlpanes = W.VerticalPanes(None, (0.5, 0.5)) self.panes.tlpanes.info = ProfilePanel(None) self.panes.tlpanes.timeline = TimelineList(None, "Timeline", self.timeline, btnCallback=self.refreshTimelineCallback, callback=self.timelineClickCallback, flags=Lists.lOnlyOne, picker=0) self.panes.tootgroup = W.Group(None) self.panes.tootgroup.toottxt = TitledEditText((56, 0, -24, 0), title="Toot", text=self.defaulttext, readonly=1, vscroll=1) # Links, Attachment, and Speech buttons self.panes.tootgroup.links = ImageWidget((-20, 0, 16, 16), pixmap=self.parent.pctLnkDis, callback=self.linksCallback) self.panes.tootgroup.attch = ImageWidget((-20, 18, 16, 16), pixmap=self.parent.pctAtcDis, callback=self.attachmentsCallback) self.panes.tootgroup.speak = ImageWidget((-20, 36, 16, 16), pixmap=self.parent.pctSpcDis, callback=self.speakCallback) # Reply/boost/favourite/bookmark buttons self.panes.tootgroup.reply = ImageWidget((4, 0, 16, 16), pixmap=self.parent.pctRplDis, callback=self.replyCallback) self.panes.tootgroup.rpnum = W.TextBox((24, 3, 28, 16), "") self.panes.tootgroup.favrt = ImageWidget((4, 18, 16, 16), pixmap=self.parent.pctFvtDis, callback=self.favouriteCallback) self.panes.tootgroup.fvnum = W.TextBox((24, 21, 28, 16), "") self.panes.tootgroup.boost = ImageWidget((4, 36, 16, 16), pixmap=self.parent.pctBstDis, callback=self.boostCallback) self.panes.tootgroup.bonum = W.TextBox((24, 39, 28, 16), "") self.panes.tootgroup.bmark = ImageWidget((4, 54, 16, 16), pixmap=self.parent.pctBkmDis, callback=self.bookmarkCallback) def close(self): del self.parent.profilewindows[self.wid] W.Window.close(self) def open(self): """ Populates an empty profile window with data for a given user. """ W.Window.open(self) self.parent.profilewindows[self.wid] = self self.account["relationship"] = self.getRelationship() if not self.account["relationship"]: self.close() return prefs = self.parent.getprefs() try: W.SetCursor("watch") pb = EasyDialogs.ProgressBar(title="Loading Profile...", maxval=10) pb.label("Formatting user name...") pb.inc() display_name = self.account["display_name"] or self.account["username"] display_name = cleanUpUnicode(display_name) acct_split = string.split(self.account["acct"], "@") acct_name = "@%s" % acct_split[0] if len(acct_split) > 1: domain_name = acct_split[1] acct_name_full = "@%s" % self.account["acct"] else: parsed_server = urlparse.urlparse(prefs.server) dprint(parsed_server) domain_name = parsed_server[1] acct_name_full = "%s@%s" % (acct_name, domain_name) self.acct_name = acct_name self.domain_name = domain_name # Window title pb.label("Setting window title...") pb.inc() self.settitle("Macstodon %s - Profile - %s" % (VERSION, acct_name_full)) # Profile banner pb.label("Loading Banner...") pb.inc() self.panes.avgroup.banner.populate(display_name, acct_name_full) if prefs.show_banners: banner = self.parent.imagehandler.getImageFromURL(self.account["header"], "banner") if banner: self.panes.avgroup.banner.setImage(banner) self.panes.avgroup.banner.enable(0) # Follow button label if self.account["relationship"]["following"]: self.panes.avgroup.followBtn.settitle("Unfollow") # Avatar pb.label("Loading Avatar...") pb.inc() if prefs.show_avatars: avatar = self.parent.imagehandler.getImageFromURL(self.account["avatar"], "account") if avatar: self.panes.avgroup.avatar.setImage(avatar) # Note pb.label("Loading Note...") pb.inc() if self.account["relationship"]["note"] != "": self.panes.avgroup.note.show(1) content = self.account["relationship"]["note"] content = string.replace(content, "\n", "\r") self.panes.avgroup.note.set(content) # Stats pb.label("Loading Stats...") pb.inc() self.panes.tlpanes.info.setToots(self.account["statuses_count"]) self.panes.tlpanes.info.setFollowers(self.account["followers_count"]) self.panes.tlpanes.info.setFollowing(self.account["following_count"]) self.panes.tlpanes.info.setLocked(self.account.get("locked", 0)) self.panes.tlpanes.info.setBot(self.account.get("bot", 0)) self.panes.tlpanes.info.setDiscoverable(self.account.get("discoverable", 0)) self.panes.tlpanes.info.setNoIndex(self.account.get("noindex", 0)) self.panes.tlpanes.info.setMoved(self.account.get("moved", 0)) self.panes.tlpanes.info.setSuspended(self.account.get("suspended", 0)) self.panes.tlpanes.info.setLimited(self.account.get("limited", 0)) # Bio pb.label("Loading Bio...") pb.inc() bio = cleanUpUnicode(self.account["note"]) bio = string.replace(bio, "<br>", "\r") bio = string.replace(bio, "<br/>", "\r") bio = string.replace(bio, "<br />", "\r") bio = string.replace(bio, "<p>", "") bio = string.replace(bio, "</p>", "\r\r") # Extract links in bio bio_le = LinkExtractor() bio_le.feed(bio) bio_le.close() # Strip remaining HTML from bio bio = re.sub('<[^<]+?>', '', bio) self.panes.tlpanes.info.setBio(bio) # Links pb.label("Loading Links...") pb.inc() linksData = [] # Field links for field in self.account["fields"]: name = cleanUpUnicode(field["name"]) if field["verified_at"] is not None: name = "ÔøΩ " + name value = cleanUpUnicode(field["value"]) field_le = LinkExtractor() field_le.feed(value) field_le.close() for desc, url in field_le.anchors.items(): value = url[0] linksData.append(name + "\r" + value) # Bio links (i.e. hashtags) for desc, url in bio_le.anchors.items(): linksData.append(desc + "\r" + url[0]) self.panes.tlpanes.info.setLinks(linksData) # Interaction Menu pb.label("Building Interaction Menu...") pb.inc() self.buildInteractionMenu() # Cleanup pb.label("Done.") pb.inc() time.sleep(0.5) del pb W.SetCursor("arrow") except KeyboardInterrupt: # the user pressed cancel in the progress bar window W.SetCursor("arrow") self.close() return None # Timeline initial_toots = int(prefs.toots_to_load_startup) if initial_toots: self.refreshTimelineCallback(initial_toots) # ####################### # Menu Handling Functions # ####################### def buildInteractionMenu(self): if self.account["relationship"]["muting"]: muteLabel = "Unmute %s" else: muteLabel = "Mute %s" if self.account["relationship"]["blocking"]: blockLabel = "Unblock %s" else: blockLabel = "Block %s" self.panes.avgroup.actionsmenu.set([ ("Mention %s" % self.acct_name, self.mentionCallback), ("Direct Message %s" % self.acct_name, self.dmCallback), "-", ("Open Original Page", self.openPageCallback), "-", ("Set note for %s" % self.acct_name, self.setNoteCallback), "-", (muteLabel % self.acct_name, self.muteCallback), (blockLabel % self.acct_name, self.blockCallback) ]) def can_logout(self, menuitem): """ Enable the Logout menu item when the Timeline window is open and active. """ return 1 def domenu_logout(self, *args): """ Log out when the Logout menu item is selected. """ self.parent.timelinewindow.close() def can_prefs(self, menuitem): """ Enable the Preferences menu item when the Timeline window is open and active. """ return 1 def domenu_prefs(self, *args): """ Open the Preferences window when the Preferences menu item is selected. """ win = self.parent.PrefsWindow() win.open() # ################## # Callback Functions # ################## def mentionCallback(self): """ Run when "Mention <user>" is selected from the interaction menu """ self.parent.tootwindow = self.parent.TootWindow(replyUser=self.account) self.parent.tootwindow.open() def dmCallback(self): """ Run when "Direct Message <user>" is selected from the interaction menu """ self.parent.tootwindow = self.parent.TootWindow(replyUser=self.account, visibility="direct") self.parent.tootwindow.open() def openPageCallback(self): """ Run when "Open Original Page" is selected from the interaction menu """ prefs = self.parent.getprefs() ic.launchurl("%s/@%s" % (prefs.server, self.account["acct"])) def setNoteCallback(self): """ Run when "Set note for <user>" is selected from the interaction menu """ note = EasyDialogs.AskString( "Please enter a new note for this user, or leave blank to remove any existing note." ) if note is not None: req_data = { "comment": note } path = "/api/v1/accounts/%s/note" % self.account["id"] data = handleRequest(self.parent, path, req_data, use_token=1, title="Setting note...") if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when setting user note:\r\r %s" % data['error_description']) elif data.get("error") is not None: okDialog("Server error when setting user note:\r\r %s" % data['error']) else: if note != "": self.panes.avgroup.note.show(1) else: self.panes.avgroup.note.show(0) self.panes.avgroup.banner.enable(1) self.panes.avgroup.banner.enable(0) self.panes.avgroup.actionsmenu.draw() self.panes.avgroup.avatar.draw() self.panes.avgroup.note.set(note) def muteCallback(self): """ Run when "Mute <user>" is selected from the interaction menu """ if self.account["relationship"]["muting"]: # already muting, undo it action = "unmute" else: # not muting yet action = "mute" try: okCancelDialog("Are you sure you want to mute @%s indefinitely?" % self.account["acct"]) except KeyboardInterrupt: return path = "/api/v1/accounts/%s/%s" % (self.account["id"], action) data = handleRequest(self.parent, path, {}, use_token=1, title="Muting user...") if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when trying to %s user:\r\r %s" % (action, data['error_description'])) elif data.get("error") is not None: okDialog("Server error when trying to %s user:\r\r %s" % (action, data['error'])) else: self.account["relationship"] = data self.buildInteractionMenu() okDialog("User %sd successfully!" % action) def blockCallback(self): """ Run when "Block <user>" is selected from the interaction menu """ if self.account["relationship"]["blocking"]: # already blocking, undo it title = "Unblocking user..." action = "unblock" else: # not blocking yet title = "Blocking user..." action = "block" try: okCancelDialog("Are you sure you want to block @%s?" % self.account["acct"]) except KeyboardInterrupt: return path = "/api/v1/accounts/%s/%s" % (self.account["id"], action) data = handleRequest(self.parent, path, {}, use_token=1, title=title) if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when %sing user:\r\r %s" % (action, data['error_description'])) elif data.get("error") is not None: okDialog("Server error when %sing user:\r\r %s" % (action, data['error'])) else: self.account["relationship"] = data self.buildInteractionMenu() okDialog("User %sed successfully!" % action) def followCallback(self): """ Run when the user clicks the Follow (Unfollow) button """ if self.account["relationship"]["following"]: # already following, undo it title = "Unfollowing user..." action = "unfollow" else: # not following yet title = "Following user..." action = "follow" path = "/api/v1/accounts/%s/%s" % (self.account["id"], action) data = handleRequest(self.parent, path, {}, use_token=1, title=title) if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when %sing user:\r\r %s" % (action, data['error_description'])) elif data.get("error") is not None: okDialog("Server error when %sing user:\r\r %s" % (action, data['error'])) else: self.account["relationship"] = data okDialog("User %sed successfully!" % action) if action == "follow": self.panes.avgroup.followBtn.settitle("Unfollow") else: self.panes.avgroup.followBtn.settitle("Follow") def timelineClickCallback(self): """ Run when the user clicks somewhere in the named timeline """ list = self.panes.tlpanes.timeline selected = list.getselection() if len(selected) < 1: self.panes.tootgroup.reply.setImage(self.parent.pctRplDis) self.panes.tootgroup.favrt.setImage(self.parent.pctFvtDis) self.panes.tootgroup.boost.setImage(self.parent.pctBstDis) self.panes.tootgroup.bmark.setImage(self.parent.pctBkmDis) self.panes.tootgroup.links.setImage(self.parent.pctLnkDis) self.panes.tootgroup.attch.setImage(self.parent.pctAtcDis) self.panes.tootgroup.speak.setImage(self.parent.pctSpcDis) self.panes.tootgroup.fvnum.set("") self.panes.tootgroup.bonum.set("") self.panes.tootgroup.rpnum.set("") self.panes.tootgroup.toottxt.setTitle("") self.panes.tootgroup.toottxt.set(self.defaulttext) return else: index = selected[0] toot = self.timeline[index] self.formatAndDisplayToot(toot) def refreshTimelineCallback(self, limit=None): """ Run when the user clicks the Refresh button above the timeline """ self.updateTimeline(limit) self.panes.tlpanes.timeline.set(self.formatTimelineForList()) def replyCallback(self): """ Run when the user clicks the "Reply" button from the timeline window. It opens up the toot window, passing the currently selected toot as a parameter. """ toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: self.parent.tootwindow = self.parent.TootWindow(replyTo=toot) self.parent.tootwindow.open() else: okDialog("Please select a toot first.") def boostCallback(self): """ Boosts a toot. Removes the boost if the toot was already boosted. """ toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: if toot["reblogged"]: # already boosted, undo it title = "Removing boost..." action = "unreblog" req_data = {} else: # not boosted yet title = "Boosting..." action = "reblog" visibility = toot["visibility"] if visibility == "limited" or visibility == "direct": visibility = "public" req_data = { "visibility": visibility } path = "/api/v1/statuses/%s/%s" % (toot["id"], action) data = handleRequest(self.parent, path, req_data, use_token=1, title=title) if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action, data['error_description'])) elif data.get("error") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action, data['error'])) else: if action == "reblog": if origToot: if origToot.get("reblog"): dprint("overwriting boosted toot") timeline[index]["reblog"] = data["reblog"] else: dprint("overwriting notification") timeline[index]["status"] = data["reblog"] else: dprint("overwriting normal toot") timeline[index] = data["reblog"] self.panes.tootgroup.boost.setImage(self.parent.pctBstClr) else: if origToot: if origToot.get("reblog"): if origToot["account"]["id"] != self.parent.currentuser["id"]: dprint("overwriting boosted toot by another user") timeline[index]["reblog"] = data else: dprint("own boosted toot, need to remove from timeline") del timeline[index] self.panes.tlpanes.timeline.set(self.formatTimelineForList()) else: dprint("overwriting notification") timeline[index]["status"] = data else: dprint("overwriting normal toot") timeline[index] = data self.panes.tootgroup.boost.setImage(self.parent.pctBstBnW) okDialog("Toot %sged successfully!" % action) else: okDialog("Please select a toot first.") def favouriteCallback(self): """ Favourites a toot. Removes the favourite if the toot was already favourited. """ toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: if toot["favourited"]: # already favourited, undo it title = "Removing favourite..." action = "unfavourite" else: # not favourited yet title = "Favouriting..." action = "favourite" path = "/api/v1/statuses/%s/%s" % (toot["id"], action) data = handleRequest(self.parent, path, {}, use_token=1, title=title) if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action[:-1], data['error_description'])) elif data.get("error") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action[:-1], data['error'])) else: if origToot: if origToot.get("reblog"): dprint("overwriting boosted toot") timeline[index]["reblog"] = data else: dprint("overwriting notification") timeline[index]["status"] = data else: dprint("overwriting normal toot") timeline[index] = data okDialog("Toot %sd successfully!" % action) if action == "favourite": self.panes.tootgroup.favrt.setImage(self.parent.pctFvtClr) else: self.panes.tootgroup.favrt.setImage(self.parent.pctFvtBnW) else: okDialog("Please select a toot first.") def bookmarkCallback(self): """ Bookmarks a toot. Removes the bookmark if the toot was already bookmarked. """ toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: if toot["bookmarked"]: # already bookmarked, undo it title = "Removing bookmark..." action = "unbookmark" else: # not bookmarked yet title = "Bookmarking..." action = "bookmark" path = "/api/v1/statuses/%s/%s" % (toot["id"], action) data = handleRequest(self.parent, path, {}, use_token=1, title=title) if not data: # handleRequest failed and should have popped an error dialog return if data.get("error_description") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action, data['error_description'])) elif data.get("error") is not None: okDialog("Server error when %sing toot:\r\r %s" % (action, data['error'])) else: if origToot: if origToot.get("reblog"): dprint("overwriting boosted toot") timeline[index]["reblog"] = data else: dprint("overwriting notification") timeline[index]["status"] = data else: dprint("overwriting normal toot") timeline[index] = data okDialog("Toot %sed successfully!" % action) if action == "bookmark": self.panes.tootgroup.bmark.setImage(self.parent.pctBkmClr) else: self.panes.tootgroup.bmark.setImage(self.parent.pctBkmBnW) else: okDialog("Please select a toot first.") def linksCallback(self): """ Displays a dialog containing the links in the toot and allows the user to open them. """ toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: content = toot["content"] # Replace HTML linebreak tags with actual linebreaks content = cleanUpUnicode(content) # Extract links le = LinkExtractor() le.feed(content) le.close() linksDialog(le) else: okDialog("Please select a toot first.") def attachmentsCallback(self): toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: attachmentsDialog(toot["media_attachments"]) else: okDialog("Please select a toot first.") def speakCallback(self): toot, origToot, timeline, index = self.getSelectedToot(resolve_boosts=1) if toot: content = toot["content"] # Replace HTML linebreak tags with actual linebreaks content = cleanUpUnicode(content) content = string.replace(content, "<br>", "\r") content = string.replace(content, "<br/>", "\r") content = string.replace(content, "<br />", "\r") content = string.replace(content, "<p>", "") content = string.replace(content, "</p>", "\r\r") # Strip all other HTML tags content = re.sub('<[^<]+?>', '', content) # Hashtags content = string.replace(content, "#", "hashtag") # HACK HACK HACK # make certain words sound better content = string.lower(content) content = string.replace(content, "macstodon", "macstodawn") content = string.replace(content, "mastodon", "mastodawn") speak(content) else: okDialog("Please select a toot first.") # #################### # Formatting Functions # #################### def formatAndDisplayToot(self, toot): """ Formats a toot for display and displays it in the bottom third """ # clear existing toot self.panes.tootgroup.reply.setImage(self.parent.pctRplDis) self.panes.tootgroup.favrt.setImage(self.parent.pctFvtDis) self.panes.tootgroup.boost.setImage(self.parent.pctBstDis) self.panes.tootgroup.bmark.setImage(self.parent.pctBkmDis) self.panes.tootgroup.links.setImage(self.parent.pctLnkDis) self.panes.tootgroup.attch.setImage(self.parent.pctAtcDis) self.panes.tootgroup.speak.setImage(self.parent.pctSpcDis) self.panes.tootgroup.fvnum.set("") self.panes.tootgroup.bonum.set("") self.panes.tootgroup.rpnum.set("") self.panes.tootgroup.toottxt.setTitle("") self.panes.tootgroup.toottxt.set("Loading toot...") display_name = toot["account"]["display_name"] or toot["account"]["username"] display_name = cleanUpUnicode(display_name) if toot["reblog"]: reblog_display_name = toot["reblog"]["account"]["display_name"] or toot["reblog"]["account"]["username"] reblog_display_name = cleanUpUnicode(reblog_display_name) title = "%s boosted %s (@%s)" % (display_name, reblog_display_name, toot["reblog"]["account"]["acct"]) content = toot["reblog"]["content"] sensitive = toot["reblog"]["sensitive"] spoiler_text = toot["reblog"]["spoiler_text"] favourites_count = toot["reblog"]["favourites_count"] reblogs_count = toot["reblog"]["reblogs_count"] replies_count = toot["reblog"]["replies_count"] favourited = toot["reblog"]["favourited"] reblogged = toot["reblog"]["reblogged"] bookmarked = toot["reblog"]["bookmarked"] else: title = "%s (@%s)" % (display_name, toot["account"]["acct"]) content = toot["content"] sensitive = toot["sensitive"] spoiler_text = toot["spoiler_text"] favourites_count = toot["favourites_count"] reblogs_count = toot["reblogs_count"] replies_count = toot["replies_count"] favourited = toot["favourited"] reblogged = toot["reblogged"] bookmarked = toot["bookmarked"] # Check for CW if sensitive: cwText = "This toot has a content warning. " \ "Press OK to view or Cancel to not view.\r\r%s" try: okCancelDialog(cwText % spoiler_text) except KeyboardInterrupt: self.panes.tootgroup.toottxt.set(self.defaulttext) return # Replace HTML linebreak tags with actual linebreaks content = cleanUpUnicode(content) content = string.replace(content, "<br>", "\r") content = string.replace(content, "<br/>", "\r") content = string.replace(content, "<br />", "\r") content = string.replace(content, "<p>", "") content = string.replace(content, "</p>", "\r\r") # Extract links le = LinkExtractor() le.feed(content) le.close() # Strip all other HTML tags content = re.sub('<[^<]+?>', '', content) # Render content into UI self.panes.tootgroup.reply.setImage(self.parent.pctRplBnW) if favourited: self.panes.tootgroup.favrt.setImage(self.parent.pctFvtClr) else: self.panes.tootgroup.favrt.setImage(self.parent.pctFvtBnW) if reblogged: self.panes.tootgroup.boost.setImage(self.parent.pctBstClr) else: self.panes.tootgroup.boost.setImage(self.parent.pctBstBnW) if bookmarked: self.panes.tootgroup.bmark.setImage(self.parent.pctBkmClr) else: self.panes.tootgroup.bmark.setImage(self.parent.pctBkmBnW) self.panes.tootgroup.links.setImage(self.parent.pctLnkBnW) self.panes.tootgroup.attch.setImage(self.parent.pctAtcBnW) self.panes.tootgroup.speak.setImage(self.parent.pctSpcBnW) self.panes.tootgroup.toottxt.setTitle(title) self.panes.tootgroup.toottxt.set(content) self.panes.tootgroup.fvnum.set(str(favourites_count)) self.panes.tootgroup.bonum.set(str(reblogs_count)) self.panes.tootgroup.rpnum.set(str(replies_count)) def formatTimelineForList(self): """ Formats toots for display in a timeline list """ listitems = [] for toot in self.timeline: if toot["reblog"]: if toot["reblog"]["sensitive"]: content = toot["reblog"]["spoiler_text"] else: content = toot["reblog"]["content"] else: if toot["sensitive"]: content = toot["spoiler_text"] else: content = toot["content"] content = cleanUpUnicode(content) # Replace linebreaks with spaces content = string.replace(content, "<br>", " ") content = string.replace(content, "<br/>", " ") content = string.replace(content, "<br />", " ") content = string.replace(content, "<p>", "") content = string.replace(content, "</p>", " ") # Strip all other HTML tags content = re.sub('<[^<]+?>', '', content) display_name = toot["account"]["display_name"] or toot["account"]["username"] display_name = cleanUpUnicode(display_name) if toot["reblog"]: reblog_display_name = toot["reblog"]["account"]["display_name"] or toot["reblog"]["account"]["username"] reblog_display_name = cleanUpUnicode(reblog_display_name) listitem = "%s boosted %s\r%s" % (display_name, reblog_display_name, content) else: listitem = "%s\r%s" % (display_name, content) listitems.append(listitem) return listitems # ################ # Helper Functions # ################ def getRelationship(self): """ Returns properties related to the relationship between the logged-in user and the user whos profile is being viewed """ # can't use urllib here because py1.5.2 doesn't support the [] syntax path = "/api/v1/accounts/relationships?id[]=" + self.account["id"] data = handleRequest(self.parent, path, use_token=1, title="Reading account relationship...") if not data: # handleRequest failed and should have popped an error dialog return 0 # if data is a list, it worked if type(data) == type([]): return data[0] # if data is a dict, it failed elif type(data) == type({}) and data.get("error") is not None: okDialog("Server error when reading account relationship:\r\r %s" % data['error']) return 0 # i don't think this is reachable, but just in case... else: okDialog("Server error when reading account relationship. Unable to determine data type.") return 0 def getSelectedToot(self, resolve_boosts=0): """ Returns the selected toot, the containing toot (if boost or notification), the timeline to which the toot belongs, and the index of the toot in the timeline. """ timeline = self.panes.tlpanes.timeline selected = timeline.getselection() if len(selected) > 0: index = selected[0] toot = self.timeline[index] timeline = self.timeline else: return None, None, None, None if toot.get("reblog") and resolve_boosts: return toot["reblog"], toot, timeline, index else: return toot, None, timeline, index def updateTimeline(self, limit = None): """ Pulls a timeline from the server and updates the global dict """ params = {} app = self.parent prefs = app.getprefs() if limit: # If a limit was explicitly set in the call, use that params["limit"] = limit else: # Otherwise, use the refresh limit from the prefs if one was set refresh_toots = int(prefs.toots_to_load_refresh) if refresh_toots: params["limit"] = refresh_toots if len(self.timeline) > 0: params["min_id"] = self.timeline[0]["id"] path = "/api/v1/accounts/%s/statuses" % self.account["id"] encoded_params = urllib.urlencode(params) if encoded_params: path = path + "?" + encoded_params data = handleRequest(self.parent, path, use_token=1, title="Updating user timeline...") if not data: # handleRequest failed and should have popped an error dialog return # if data is a list, it worked if type(data) == type([]): for i in range(len(data)-1, -1, -1): self.timeline.insert(0, data[i]) self.timeline = self.timeline[:int(prefs.toots_per_timeline)] # if data is a dict, it failed elif type(data) == type({}) and data.get("error") is not None: okDialog("Server error when refreshing timeline:\r\r %s" % data['error']) # i don't think this is reachable, but just in case... else: okDialog("Server error when refreshing timeline. Unable to determine data type.")