-
Notifications
You must be signed in to change notification settings - Fork 0
/
MacstodonHelpers.py
1029 lines (911 loc) · 35.9 KB
/
MacstodonHelpers.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 aetoolsimport EasyDialogsimport Fmimport formatterimport htmllibimport icimport Listsimport MacOSimport macfsimport osimport Qdimport QuickDrawimport stringimport TEimport timeimport urllibimport urlparseimport Wfrom TextEdit import *from Wlists import Listtry: import macspeechexcept ImportError: macspeech = None# ########### My Imports# ##########from MacstodonConstants import DEBUG, VERSION# #################### Third-Party Imports# ###################import third_party.Say_Suitefrom third_party.PixMapWrapper import PixMapWrapper# ########## Functions# ######### def buildTimelinePicker(app): menu = [ ("Home", "home"), ("Local", "local"), ("Federated", "federated"), ("Notifications", "notifications"), ("Bookmarks", "bookmarks"), ("Favourites", "favourites"), ("Private Mentions", "mentions"), ("Hashtag...", "__hashtag"), "-" ] for list in app.lists: menu.append((list["title"], list["id"])) return menudef getFilenameFromURL(url): """ Returns the file name, including extension, from a URL. i.e. http://www.google.ca/foo/bar/baz/asdf.jpg returns "asdf.jpg" """ parsed_url = urlparse.urlparse(url) path = parsed_url[2] file_name = os.path.basename(string.replace(path, "/", ":")) return file_namedef cleanUpUnicode(content): """ Do the best we can to manually clean up unicode stuff """ # The text is UTF-8, but is being interpreted as MacRoman. # First step is to convert the extended characters into # MacRoman so that they display correctly. content = string.replace(content, "√Ñ", "Ä") content = string.replace(content, "√Ö", "Å") content = string.replace(content, "√á", "Ç") content = string.replace(content, "√â", "É") content = string.replace(content, "√ë", "Ñ") content = string.replace(content, "√ñ", "Ö") content = string.replace(content, "√ú", "Ü") content = string.replace(content, "√°", "á") content = string.replace(content, "√†", "à") content = string.replace(content, "√¢", "â") content = string.replace(content, "√§", "ä") content = string.replace(content, "√£", "ã") content = string.replace(content, "√•", "å") content = string.replace(content, "√ß", "ç") content = string.replace(content, "√©", "é") content = string.replace(content, "√®", "è") content = string.replace(content, "√™", "ê") content = string.replace(content, "√´", "ë") content = string.replace(content, "√≠", "í") content = string.replace(content, "√¨", "ì") content = string.replace(content, "√Æ", "î") content = string.replace(content, "√Ø", "ï") content = string.replace(content, "√±", "ñ") content = string.replace(content, "√≥", "ó") content = string.replace(content, "√≤", "ò") content = string.replace(content, "√¥", "ô") content = string.replace(content, "√∂", "ö") content = string.replace(content, "√µ", "õ") content = string.replace(content, "√∫", "ú") content = string.replace(content, "√π", "ù") content = string.replace(content, "√ª", "û") content = string.replace(content, "√º", "ü") content = string.replace(content, "‚Ć", "†") content = string.replace(content, "¬∞", "°") content = string.replace(content, "¬¢", "¢") content = string.replace(content, "¬£", "£") content = string.replace(content, "¬ß", "§") content = string.replace(content, "‚Ä¢", "•") content = string.replace(content, "¬∂", "¶") content = string.replace(content, "√ü", "ß") content = string.replace(content, "¬Æ", "®") content = string.replace(content, "¬©", "©") content = string.replace(content, "‚Ñ¢", "™") content = string.replace(content, "¬¥", "´") content = string.replace(content, "¬®", "¨") content = string.replace(content, "‚â†", "≠") content = string.replace(content, "√Ü", "Æ") content = string.replace(content, "√ò", "Ø") content = string.replace(content, "‚àû", "∞") content = string.replace(content, "¬±", "±") content = string.replace(content, "‚â§", "≤") content = string.replace(content, "‚â•", "≥") content = string.replace(content, "¬•", "¥") content = string.replace(content, "¬µ", "µ") content = string.replace(content, "‚àÇ", "∂") content = string.replace(content, "‚àë", "∑") content = string.replace(content, "‚àè", "∏") content = string.replace(content, "œÄ", "π") content = string.replace(content, "‚à´", "∫") content = string.replace(content, "¬™", "ª") content = string.replace(content, "¬∫", "º") content = string.replace(content, "Œ©", "Ω") content = string.replace(content, "√¶", "æ") content = string.replace(content, "√∏", "ø") content = string.replace(content, "¬ø", "¿") content = string.replace(content, "¬°", "¡") content = string.replace(content, "¬¨", "¬") content = string.replace(content, "‚àö", "√") content = string.replace(content, "∆í", "ƒ") content = string.replace(content, "‚âà", "≈") content = string.replace(content, "‚àÜ", "∆") content = string.replace(content, "¬´", "«") content = string.replace(content, "¬ª", "»") content = string.replace(content, "‚Ķ", "…") content = string.replace(content, "√Ä", "À") content = string.replace(content, "√É", "Ã") content = string.replace(content, "√ï", "Õ") content = string.replace(content, "≈í", "Œ") content = string.replace(content, "≈ì", "œ") content = string.replace(content, "‚Äì", "–") content = string.replace(content, "‚Äî", "—") content = string.replace(content, "‚Äú", "“") content = string.replace(content, "‚Äù", "”") content = string.replace(content, "‚Äò", "‘") content = string.replace(content, "‚Äô", "’") content = string.replace(content, "√∑", "÷") content = string.replace(content, "‚óä", "◊") content = string.replace(content, "√ø", "ÿ") content = string.replace(content, "≈∏", "Ÿ") content = string.replace(content, "‚ÅÑ", "⁄") content = string.replace(content, "‚Ǩ", "€") content = string.replace(content, "‚Äπ", "‹") content = string.replace(content, "‚Ä∫", "›") content = string.replace(content, "Ô¨Å", "fi") content = string.replace(content, "Ô¨Ç", "fl") content = string.replace(content, "‚Ä°", "‡") content = string.replace(content, "¬∑", "·") content = string.replace(content, "‚Äö", "‚") content = string.replace(content, "‚Äû", "„") content = string.replace(content, "‚Ä∞", "‰") content = string.replace(content, "√Ç", "Â") content = string.replace(content, "√ä", "Ê") content = string.replace(content, "√Å", "Á") content = string.replace(content, "√ã", "Ë") content = string.replace(content, "√à", "È") content = string.replace(content, "√ç", "Í") content = string.replace(content, "√é", "Î") content = string.replace(content, "√è", "Ï") content = string.replace(content, "√å", "Ì") content = string.replace(content, "√ì", "Ó") content = string.replace(content, "√î", "Ô") content = string.replace(content, "Ô£ø", "") content = string.replace(content, "√í", "Ò") content = string.replace(content, "√ö", "Ú") content = string.replace(content, "√õ", "Û") content = string.replace(content, "√ô", "Ù") content = string.replace(content, "ƒ±", "ı") content = string.replace(content, "ÀÜ", "ˆ") content = string.replace(content, "Àú", "˜") content = string.replace(content, "¬Ø", "¯") content = string.replace(content, "Àò", "˘") content = string.replace(content, "Àô", "˙") content = string.replace(content, "Àö", "˚") content = string.replace(content, "¬∏", "¸") content = string.replace(content, "Àù", "˝") content = string.replace(content, "Àõ", "˛") content = string.replace(content, "Àá", "ˇ") # Next, convert unicode codepoints back into characters content = string.replace(content, "\\u003e", ">") content = string.replace(content, "\\u003c", "<") content = string.replace(content, "\\u0026", "&") # Lastly, convert HTML entities back into characters content = string.replace(content, """, '"') content = string.replace(content, "'", "'") content = string.replace(content, "&", "&") content = string.replace(content, ">", ">") content = string.replace(content, "<", "<") # After conversion, any characters above byte 127 are # outside the MacRoman character set and should be # stripped. for char in content: if ord(char) > 127: content = string.replace(content, char, "") return contentdef decodeJson(data): """ 'Decode' the JSON by taking the advantage of the fact that it is very similar to a Python dict. This is a terrible hack, and you should never do this anywhere because we're literally eval()ing untrusted data from the 'net. I'm only doing it because it's fast and there's not a lot of other options for parsing JSON data in Python 1.5. """ data = string.replace(data, '":null', '":None') data = string.replace(data, '":false', '":0') data = string.replace(data, '":true', '":1') data = eval(data) return datadef dprint(text): """ Prints a string to stdout if and only if DEBUG is true """ if DEBUG: print textdef okDialog(text, size=None): """ Draws a modal dialog box with the given text and an OK button to dismiss the dialog. """ if not size: size = (360, 120) window = W.ModalDialog(size, "Macstodon %s - Message" % VERSION) window.label = W.TextBox((10, 10, -10, -40), text) window.ok_btn = W.Button((-80, -30, -10, -10), "OK", window.close) window.setdefaultbutton(window.ok_btn) window.open()def okCancelDialog(text, size=None): """ Draws a modal dialog box with the given text and OK/Cancel buttons. The OK button will close the dialog. The Cancel button will raise an Exception, which the caller is expected to catch. """ if not size: size = (360, 120) global dialogWindow dialogWindow = W.ModalDialog(size, "Macstodon %s - Message" % VERSION) def dialogExceptionCallback(): dialogWindow.close() raise KeyboardInterrupt dialogWindow.label = W.TextBox((10, 10, -10, -40), text) dialogWindow.cancel_btn = W.Button((-160, -30, -90, -10), "Cancel", dialogExceptionCallback) dialogWindow.ok_btn = W.Button((-80, -30, -10, -10), "OK", dialogWindow.close) dialogWindow.setdefaultbutton(dialogWindow.ok_btn) dialogWindow.open()def attachmentsDialog(media_attachments): """ Draws a modal dialog box with Download and Close buttons. Between the text and buttons a list is drawn containing attachments. Clicking on an attachment, then clicking on the Download button will save the contents of the attachment to disk. Clicking the Close button will close the window. """ if len(media_attachments) == 0: okDialog("The selected toot contains no attachments.") return global attachmentsWindow, attachmentsData attachmentsFormatted = [] attachmentsData = [] for attachment in media_attachments: if attachment["description"] is not None: desc = cleanUpUnicode(attachment["description"]) listStr = desc + "\r" else: desc = None listStr = "No description\r" if attachment["type"] == "image": listStr = listStr + "Image, %s" % attachment["meta"]["original"]["size"] elif attachment["type"] == "video": listStr = listStr + "Video, %s, %s" % (attachment["meta"]["size"], attachment["meta"]["length"]) elif attachment["type"] == "gifv": listStr = listStr + "GIFV, %s, %s" % (attachment["meta"]["size"], attachment["meta"]["length"]) elif attachment["type"] == "audio": listStr = listStr + "Audio, %s" % attachment["meta"]["length"] else: listStr = listStr + "Unknown" attachmentsFormatted.append(listStr) attachmentsData.append({"url": attachment["url"], "alt": desc}) attachmentsWindow = W.ModalDialog((360, 240), "Macstodon %s - Attachments" % VERSION) def openAttachmentCallback(): """ Run when the user clicks the Download button """ selected = attachmentsWindow.attachments.getselection() if len(selected) > 0: url = attachmentsData[selected[0]]["url"] default_file_name = getFilenameFromURL(url) fss, ok = macfs.StandardPutFile('Save as:', default_file_name) if not ok: return 1 file_path = fss.as_pathname() file_name = os.path.split(file_path)[-1] urllib.urlretrieve(url, file_path) okDialog("Successfully downloaded '%s'!" % file_name) else: okDialog("Please select an attachment first.") def altTextCallback(): """ Run when the user clicks the Alt Text button """ selected = attachmentsWindow.attachments.getselection() if len(selected) > 0: alt_text = attachmentsData[selected[0]]["alt"] if alt_text is not None: okDialog(alt_text) else: okDialog("This attachment doesn't have any alt text.") else: okDialog("Please select an attachment first.") text = "The following attachments were found in the selected toot. " \ "Click on an attachment in the list, then click on the Download button " \ "to download it to your computer. You can also click on the Alt Text " \ "button to view the attachment's alt text in a dialog." attachmentsWindow.label = W.TextBox((10, 10, -10, -60), text) attachmentsWindow.attachments = TwoLineListWithFlags((10, 76, -10, -42), attachmentsFormatted, callback=None, flags = Lists.lOnlyOne, cols = 1, typingcasesens=0) attachmentsWindow.alt_btn = W.Button((-240, -30, -170, -10), "Alt Text", altTextCallback) attachmentsWindow.close_btn = W.Button((-160, -30, -90, -10), "Close", attachmentsWindow.close) attachmentsWindow.download_btn = W.Button((-80, -30, -10, -10), "Download", openAttachmentCallback) attachmentsWindow.setdefaultbutton(attachmentsWindow.close_btn) attachmentsWindow.open()def linksDialog(le): """ Draws a modal dialog box with Open and Close buttons. Between the text and buttons a list is drawn containing links. Clicking on a link, then clicking on the Open button will open the link using the user's web browser. Clicking the Close button will close the window. """ global linksWindow, linksFormatted linksFormatted = [] for desc, url in le.anchors.items(): linksFormatted.append(desc + "\r" + url[0]) if len(linksFormatted) == 0: okDialog("The selected toot contains no links.") return linksWindow = W.ModalDialog((240, 240), "Macstodon %s - Links" % VERSION) def openLinkCallback(): """ Run when the user clicks the Open button """ selected = linksWindow.links.getselection() if len(selected) > 0: linkString = linksFormatted[selected[0]] linkParts = string.split(linkString, "\r") ic.launchurl(linkParts[1]) else: okDialog("Please select a link first.") text = "The following links were found in the selected toot. " \ "Click on a link in the list, then click on the Open button " \ "to open it with your browser." linksWindow.label = W.TextBox((10, 10, -10, -40), text) linksWindow.links = TwoLineListWithFlags((10, 56, -10, -42), linksFormatted, callback=None, flags = Lists.lOnlyOne, cols = 1, typingcasesens=0) linksWindow.close_btn = W.Button((-160, -30, -90, -10), "Close", linksWindow.close) linksWindow.open_btn = W.Button((-80, -30, -10, -10), "Open", openLinkCallback) linksWindow.setdefaultbutton(linksWindow.close_btn) linksWindow.open()def handleRequest(app, path, data=None, use_token=0, title="Working..."): """ HTTP request wrapper """ try: W.SetCursor("watch") pb = EasyDialogs.ProgressBar(title=title, maxval=3) if data == {}: data = "" elif data: data = urllib.urlencode(data) prefs = app.getprefs() url = "%s%s" % (prefs.server, path) dprint(url) dprint(data) dprint("connecting") pb.label("Connecting...") pb.inc() try: if use_token: urlopener = TokenURLopener(prefs.token) handle = urlopener.open(url, data) else: handle = urllib.urlopen(url, data) except IOError: del pb W.SetCursor("arrow") errmsg = "Unable to open a connection to: %s.\rPlease check that your SSL proxy is working properly and that the URL starts with 'http'." okDialog(errmsg % url) return None except TypeError: del pb W.SetCursor("arrow") errmsg = "The provided URL is malformed: %s.\rPlease check that you have typed the URL correctly." okDialog(errmsg % url) return None dprint("reading http headers") headers = handle.info() dprint(headers) length = int(headers["Content-Length"]) dprint("reading http body") pb.label("Fetching data...") pb.inc() try: data = handle.read(length) except IOError: del pb W.SetCursor("arrow") errmsg = "The connection was closed by the remote server while Macstodon was reading data.\rPlease check that your SSL proxy is working properly." okDialog(errmsg) return None try: handle.close() except IOError: pass pb.label("Parsing data...") pb.inc() dprint("parsing response json") try: decoded = decodeJson(data) dprint(decoded) pb.label("Done.") pb.inc() time.sleep(0.5) del pb W.SetCursor("arrow") return decoded except: del pb W.SetCursor("arrow") dprint("ACK! JSON Parsing failure :(") dprint("This is what came back from the server:") dprint(data) okDialog("Error parsing JSON response from the server.") return None except KeyboardInterrupt: # the user pressed cancel in the progress bar window W.SetCursor("arrow") return Nonedef getCurrentUser(app): """ Gets the currently logged in user. """ path = "/api/v1/accounts/verify_credentials" data = handleRequest(app, path, None, use_token=1, title = "Getting 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 getting current user:\r\r %s" % data['error_description']) elif data.get("error") is not None: okDialog("Server error when getting current user:\r\r %s" % data['error']) else: return datadef getLists(app): """ Gets the user's lists. """ path = "/api/v1/lists" data = handleRequest(app, path, None, use_token=1, title = "Getting Lists...") if data is None: # handleRequest failed and should have popped an error dialog return # if data is a list, it worked if type(data) == type([]): return data # otherwise data is an error dict if data.get("error_description") is not None: okDialog("Server error when getting lists:\r\r %s" % data['error_description']) elif data.get("error") is not None: okDialog("Server error when getting lists:\r\r %s" % data['error']) else: okDialog("Server error when getting lists. Unable to determine data type.")def speak(text): """ Says a thing. """ if macspeech: macspeech.SpeakString(text) while macspeech.Busy(): pass else: try: talker = Say("MACS") talker.say(text) except MacOS.Error, arg: if arg[0] == -1712: okDialog("Apple Event timed out! Please make sure that AppleScript and the Scriptable Finder (7.1.3 or above) are installed.") elif arg[0] == -609: okDialog('Connection invalid! Please make sure the Finder is running, then try this again.') else: okDialog("Unhandled MacOS Error %s: %s" % (arg[0], arg[1])) except aetools.Error, arg: if arg[0] == -1708: okDialog("Unhandled event! Are AppleScript and the Say scripting addition installed?") elif arg[0] == -240: okDIalog('No Synth Found! Please select a voice using the "Speech" control panel, then try this again.') else: okDialog("Unhandled AETools Error %s: %s" % (arg[0], arg[1]))# ######## Classes# #######class ImageWidget(W.ClickableWidget): """ A widget that displays an image. The image should be passed in as a PixMapWrapper. """ def __init__(self, possize, pixmap=None, callback=None): W.ClickableWidget.__init__(self, possize) self._callback = callback self._enabled = 1 # Set initial image self._imgloaded = 0 self._pixmap = None if pixmap: self.setImage(pixmap) def click(self, point, modifiers): """ Runs the callback if the user clicks on the image """ if not self._enabled: return if self._callback: return W.CallbackCall(self._callback, 0) def close(self): """ Destroys the widget and frees up its memory """ W.Widget.close(self) del self._imgloaded del self._pixmap def setImage(self, pixmap): """ Loads a new image into the widget. The image will be automatically scaled to the size of the widget. """ self._pixmap = pixmap self._imgloaded = 1 if self._parentwindow: self.draw() def clearImage(self): """ Unloads the image from the widget without destroying the widget. Use this to make the widget draw an empty square. """ self._imgloaded = 0 Qd.EraseRect(self._bounds) if self._parentwindow: self.draw() self._pixmap = None def draw(self, visRgn = None): """ Draw the image within the widget if it is loaded """ if self._visible: if self._imgloaded: if isinstance(self._pixmap, PixMapWrapper): self._pixmap.blit( x1=self._bounds[0], y1=self._bounds[1], x2=self._bounds[2], y2=self._bounds[3], port=self._parentwindow.wid.GetWindowPort() ) else: Qd.SetPort(self._parentwindow.wid.GetWindowPort()) Qd.DrawPicture(self._pixmap, self._bounds)class LinkExtractor(htmllib.HTMLParser): """ A very basic link extractor that gets the URL and associated text. Sourced from: https://oreilly.com/library/view/python-standard-library/0596000960/ch05s05.html """ def __init__(self, verbose=0): self.anchors = {} f = formatter.NullFormatter() htmllib.HTMLParser.__init__(self, f, verbose) def anchor_bgn(self, href, name, type): self.save_bgn() self.anchor = href def anchor_end(self): text = string.strip(self.save_end()) if self.anchor and text: self.anchors[text] = self.anchors.get(text, []) + [self.anchor]class ProfileBanner(ImageWidget): """ The ProfileBanner is just an ImageWidget that renders text atop it at a fixed location, using a specific font and style. """ def __init__(self, possize, drop_shadow, pixmap=None, display_name="", acct_name=""): ImageWidget.__init__(self, possize, pixmap) self.display_name = display_name self.acct_name = acct_name self.drop_shadow = drop_shadow def draw(self, visRgn = None): """ Draws the profile banner text using pure QuickDraw (instead of widgets) """ Qd.SetPort(self._parentwindow.wid.GetWindowPort()) ImageWidget.draw(self, visRgn) Qd.TextFont(Fm.GetFNum("Geneva")) # Display Name Qd.TextSize(14) Qd.TextFace(QuickDraw.bold) Qd.RGBForeColor((0,0,0)) if self.drop_shadow: rect = (self._bounds[0] + 7, self._bounds[1] + 70, self._bounds[2], 0) TE.TETextBox(self.display_name, rect, teJustLeft) Qd.RGBForeColor((65535,65535,65535)) rect = (self._bounds[0] + 5, self._bounds[1] + 68, self._bounds[2], 0) TE.TETextBox(self.display_name, rect, teJustLeft) # Account Name Qd.TextSize(9) Qd.TextFace(QuickDraw.normal) Qd.RGBForeColor((0,0,0)) if self.drop_shadow: rect = (self._bounds[0] + 7, self._bounds[1] + 90, self._bounds[2], 0) TE.TETextBox(self.acct_name, rect, teJustLeft) Qd.RGBForeColor((65535,65535,65535)) rect = (self._bounds[0] + 5, self._bounds[1] + 88, self._bounds[2], 0) TE.TETextBox(self.acct_name, rect, teJustLeft) Qd.RGBForeColor((0,0,0)) Qd.TextFont(Fm.GetFNum("Monaco")) def populate(self, display_name=None, acct_name=None): """ Sets the display and account names. """ if display_name: self.display_name = display_name if acct_name: self.acct_name = acct_nameclass ProfilePanel(W.Group): """ The ProfilePanel is my poor man's implementation of tabs. It uses three buttons to swap out the widget beneath them. """ def __init__(self, possize): W.Group.__init__(self, possize) self.title = W.TextBox((0, 2, 0, 16), "Bio") self.btnStats = W.Button((-40, 0, 40, 16), "Stats", self.statsCallback) self.btnLinks = W.Button((-90, 0, 40, 16), "Links", self.linksCallback) self.btnBio = W.Button((-140, 0, 40, 16), "Bio", self.bioCallback) # Bio editor = W.EditText((0, 24, -15, 0), "", readonly=1) self._bary = W.Scrollbar((-16, 24, 0, 0), editor.vscroll, max=32767) self.bioText = editor # Stats & Links self.list = TwoLineListWithFlags((0, 24, 0, 0), [], callback=None, flags = Lists.lOnlyOne, cols = 1, typingcasesens=0) self.linksData = [] self.statsData = [] self.toots = 0 self.following = 0 self.followers = 0 self.locked = "No" self.bot = "No" self.group = "No" self.discoverable = "No" self.noindex = "No" self.moved = "No" self.suspended = "No" self.limited = "No" # hide stats/links by default self.list.show(0) self.bioText.select(0) def statsCallback(self): """ Shows the stats pane and hides bio/links """ self.title.set("Stats") self.bioText.show(0) self.bioText.enable(0) self.bioText.select(0) self.bioText._selectable = 0 self._bary.show(0) self.list.show(1) self.list.enable(1) self.list.select(1) self.btnStats.draw() self.btnLinks.draw() self.btnBio.draw() self.list.set(self.statsData) def linksCallback(self): """ Shows the links pane and hides bio/stats """ self.title.set("Links") self.bioText.show(0) self.bioText.enable(0) self.bioText.select(0) self.bioText._selectable = 0 self._bary.show(0) self.list.show(1) self.list.enable(1) self.list.select(1) self.btnStats.draw() self.btnLinks.draw() self.btnBio.draw() self.list.set(self.linksData) def bioCallback(self): """ Shows the bio pane and hides stats/links """ self.title.set("Bio") self.list.show(0) self.list.enable(0) self.list.select(0) self.bioText.show(1) self.bioText.enable(1) self.bioText._selectable = 1 self._bary.show(1) self._bary.draw() self.btnStats.draw() self.btnLinks.draw() self.btnBio.draw() def setBio(self, value): """ Updates the content of the bio text """ self.bioText.set(value) def collectStatsData(self): """ Updates the content of the stats list """ self.statsData = [ "Toots\r%s" % self.toots, "Following\r%s" % self.following, "Followers\r%s" % self.followers, "Locked\r%s" % self.locked, "Bot\r%s" % self.bot, "Group\r%s" % self.group, "Discoverable\r%s" % self.discoverable, "NoIndex\r%s" % self.noindex, "Moved\r%s" % self.moved, "Suspended\r%s" % self.suspended, "Limited\r%s" % self.limited ] self.list.set(self.statsData) def setLinks(self, linksData): """ Updates the content of the links list """ self.linksData = linksData self.list.set(self.linksData) def setToots(self, num): """ Updates the user's toot count """ self.toots = num self.collectStatsData() def setFollowers(self, num): """ Updates the user's followers count """ self.followers = num self.collectStatsData() def setFollowing(self, num): """ Updates the user's following count """ self.following = num self.collectStatsData() def setLocked(self, flag): if flag: self.locked = "Yes" else: self.locked = "No" self.collectStatsData() def setBot(self, flag): if flag: self.bot = "Yes" else: self.bot = "No" self.collectStatsData() def setDiscoverable(self, flag): if flag: self.discoverable = "Yes" else: self.discoverable = "No" self.collectStatsData() def setNoIndex(self, flag): if flag: self.noindex = "Yes" else: self.noindex = "No" self.collectStatsData() def setMoved(self, flag): if flag: self.moved = "Yes" else: self.moved = "No" self.collectStatsData() def setSuspended(self, flag): if flag: self.suspended = "Yes" else: self.suspended = "No" self.collectStatsData() def setLimited(self, flag): if flag: self.limited = "Yes" else: self.limited = "No" self.collectStatsData()class Say(third_party.Say_Suite.Say, aetools.TalkTo): """ Implements speech for 68K Macs using AppleScript. """ passclass TitledEditText(W.Group): """ A text edit field with a title and optional scrollbars attached to it. Shamelessly stolen from MacPython's PyEdit. Modified to also allow setting the title, and add scrollbars. """ def __init__(self, possize, title, text="", readonly=0, vscroll=0, hscroll=0): W.Group.__init__(self, possize) self.title = W.TextBox((0, 0, 0, 16), title) if vscroll and hscroll: editor = W.EditText((0, 16, -15, -15), text, readonly=readonly) self._barx = W.Scrollbar((0, -16, -15, 16), editor.hscroll, max=32767) self._bary = W.Scrollbar((-16, 16,0, -15), editor.vscroll, max=32767) elif vscroll: editor = W.EditText((0, 16, -15, 0), text, readonly=readonly) self._bary = W.Scrollbar((-16, 16, 0, 0), editor.vscroll, max=32767) elif hscroll: editor = W.EditText((0, 16, 0, -15), text, readonly=readonly) self._barx = W.Scrollbar((0, -16, 0, 16), editor.hscroll, max=32767) else: editor = W.EditText((0, 16, 0, 0), text, readonly=readonly) self.edit = editor def setTitle(self, value): self.title.set(value) def set(self, value): self.edit.set(value) def get(self): return self.edit.get()class TokenURLopener(urllib.FancyURLopener): """ Extends urllib.FancyURLopener to add the Authorization header with a bearer token. """ def __init__(self, token, *args): apply(urllib.FancyURLopener.__init__, (self,) + args) self.addheaders.append(("Authorization", "Bearer %s" % token))class TwoLineListWithFlags(List): """ Modification of MacPython's TwoLineList to support flags. """ LDEF_ID = 468 def createlist(self): import List self._calcbounds() self.SetPort() rect = self._bounds rect = rect[0]+1, rect[1]+1, rect[2]-16, rect[3]-1 self._list = List.LNew(rect, (0, 0, 1, 0), (0, 28), self.LDEF_ID, self._parentwindow.wid, 0, 1, 0, 1) self._list.selFlags = self._flags self.set(self.items)class TimelineList(W.Group): """ A TwoLineListWithFlags that also has a title attached to it. Based on TitledEditText. """ def __init__(self, possize, title, items = None, picker = 1, pickerItems = None, btnCallback = None, callback = None, pickerCallback = None, flags = 0, cols = 1, typingcasesens=0): W.Group.__init__(self, possize) if picker: self.listpicker = W.PopupWidget((-68, 0, 16, 16), pickerItems, pickerCallback) self.title = W.TextBox((0, 2, -68, 16), title) self.btn = W.Button((-50, 0, 0, 16), "Refresh", btnCallback) self.list = TwoLineListWithFlags((0, 24, 0, 0), items, callback, flags, cols, typingcasesens) def setListPicker(self, value): self.listpicker.set(value) def setTitle(self, value): self.title.set(value) def set(self, items): self.list.set(items) def get(self): return self.list.items def getselection(self): return self.list.getselection() def setselection(self, selection): return self.list.setselection(selection)