Sort posts before selecting one by number
[jan-pona-mute.git] / jan-pona-mute.py
index 332cf17bcb9573a147d33a86607d3caac7166200..7b7e1151e4b88f3304cd3f0bd02dfb570f04a57e 100755 (executable)
@@ -22,16 +22,6 @@ import cmd
 import sys
 import os
 
-# Command abbreviations
-_ABBREVS = {
-    "q":    "quit",
-    "p":    "print",
-    "c":    "comments",
-    "r":    "reload",
-    "n":    "notifications",
-    "e":    "edit",
-}
-
 _RC_PATHS = (
     "~/.config/jan-pona-mute/login",
     "~/.jan-pona-mute.d/login"
@@ -56,6 +46,17 @@ _EDITORS = (
     "ed"
 )
 
+shortcuts = {
+    "q":    "quit",
+    "p":    "preview",
+    "c":    "comments",
+    "r":    "reload",
+    "n":    "notifications",
+    "e":    "edit",
+    "d":    "delete",
+    "g":    "notifications reload",
+}
+
 def get_rcfile():
     """Init file finder"""
     for path in _RC_PATHS:
@@ -108,7 +109,8 @@ class DiasporaClient(cmd.Cmd):
 
     connection = None
     notifications = []
-    index = None
+    home = None
+    numbers_refer_to = None
     post = None
     post_cache = {} # key is self.post.uid, and notification.id
 
@@ -134,8 +136,8 @@ then use the 'login' command to log in. If everything works as
 intended, use the 'save' command to save these commands to an init
 file.
 
-Once you've listed things such as notifications, enter a number to
-select the corresponding item.
+Once you've listed things such as notifications or the home stream,
+enter a number to select the corresponding item.
 """)
 
     def do_account(self, account):
@@ -149,12 +151,13 @@ select the corresponding item.
 
     def do_info(self, line):
         """Get some info about things. By default, it is info about yourself."""
-        print("Info about yourself:")
+        print(self.header("Info"))
         print("Username: %s" % self.username)
         print("Password: %s" % ("None" if self.password == None else "set"))
         print("Pod:      %s" % self.pod)
         print("Pager:    %s" % self.pager)
         print("Editor:   %s" % self.editor)
+        print("Cache:    %s posts" % len(self.post_cache))
 
     def do_password(self, password):
         """Set the password."""
@@ -229,11 +232,12 @@ select the corresponding item.
         elif self.password == None:
             print("Use the 'password' command.")
         else:
+            print("Setting up a connection...")
             self.connection = diaspy.connection.Connection(
                 pod = "https://%s" % self.pod, username = self.username, password = self.password)
             try:
+                print("Logging in...")
                 self.connection.login()
-                self.onecmd("notifications")
             except diaspy.errors.LoginError:
                 print("Login failed.")
 
@@ -255,7 +259,7 @@ select the corresponding item.
         """List notifications. Use 'notifications reload' to reload them."""
         if line == "" and self.notifications:
             print("Redisplaying the notifications in the cache.")
-            print("Use the 'reload' argument to reload them.")
+            print("Use 'notifications reload' to reload them.")
         elif line == "reload" or not self.notifications:
             if self.connection == None:
                 print("Use the 'login' command, first.")
@@ -266,8 +270,12 @@ select the corresponding item.
             return
         if self.notifications:
             for n, notification in enumerate(self.notifications):
-                print(self.header("%2d. %s %s") % (n+1, notification.when(), notification))
+                if notification.unread:
+                    print(self.header("%2d. %s %s") % (n+1, notification.when(), notification))
+                else:
+                    print("%2d. %s %s" % (n+1, notification.when(), notification))
             print("Enter a number to select the notification.")
+            self.numbers_refer_to = 'notifications'
         else:
             print("There are no notifications. ðŸ˜¢")
 
@@ -283,8 +291,8 @@ select the corresponding item.
 
         # Expand abbreviated commands
         first_word = line.split()[0].strip()
-        if first_word in _ABBREVS:
-            full_cmd = _ABBREVS[first_word]
+        if first_word in shortcuts:
+            full_cmd = shortcuts[first_word]
             expanded = line.replace(first_word, full_cmd, 1)
             return self.onecmd(expanded)
 
@@ -293,26 +301,34 @@ select the corresponding item.
 
     def do_show(self, line):
         """Show the post given by the index number.
-The index number must refer to the current list of notifications."""
-        if not self.notifications:
-            print("No notifications were loaded.")
-            return
-        if line == "":
-            print("The 'show' command takes a notification number.")
-            return
-        try:
-            n = int(line.strip())
-            notification = self.notifications[n-1]
-            self.index = n
-        except ValueError:
-            print("The 'show' command takes a notification number but '%s' is not a number" % line)
+The index number must refer to the current list of notifications
+or the home stream. If no index number is given, show the current
+post again."""
+        if not self.notifications and not self.home:
+            print("Use the 'login' command to load notifications.")
             return
-        except IndexError:
-            print("Index too high!")
+        if line == "" and self.post == None:
+            print("Please specify a number.")
             return
-
-        self.show(notification)
-        self.load(notification.about())
+        if line != "":
+            try:
+                n = int(line.strip())
+                if self.numbers_refer_to == 'notifications':
+                    notification = self.notifications[n-1]
+                    self.show(notification)
+                    self.load(notification.about())
+                elif self.numbers_refer_to == 'home':
+                    posts = sorted(self.home, key=lambda x: x.data()["created_at"])
+                    self.post = posts[n-1]
+                else:
+                    print("Internal error: not sure what numbers '%s' refer to." % self.numbers_refer_to)
+                    return
+            except ValueError:
+                print("The 'show' command takes a notification number but '%s' is not a number" % line)
+                return
+            except IndexError:
+                print("Index too high!")
+                return
 
         print()
         self.show(self.post)
@@ -405,8 +421,7 @@ Use the 'edit' command to edit notes."""
             notes = self.get_notes()
             if notes:
                 try:
-                    with open(self.get_note_path(notes[n-1]), mode = 'r', encoding = 'utf-8') as fp:
-                        line = fp.read()
+                    line = self.read_note(notes[n-1])
                     print("Using note #%d: %s" % (n, notes[n-1]))
                 except IndexError:
                     print("Use the 'list notes' command to list valid numbers.")
@@ -422,6 +437,34 @@ Use the 'edit' command to edit notes."""
         self.undo.append("delete comment %s from %s" % (comment.id, self.post.id))
         print("Comment posted.")
 
+    def do_post(self, line):
+        """Write a post on the current stream.
+If you just use a number as your post, it will refer to a note.
+Use the 'edit' command to edit notes."""
+        if self.home == None:
+            self.home = diaspy.streams.Stream(self.connection)
+        try:
+            # if the post is just a number, use a note to post
+            n = int(line.strip())
+            notes = self.get_notes()
+            if notes:
+                try:
+                    line = self.read_note(notes[n-1])
+                    print("Using note #%d: %s" % (n, notes[n-1]))
+                except IndexError:
+                    print("Use the 'list notes' command to list valid numbers.")
+                    return
+            else:
+                print("There are no notes to use.")
+                return
+        except ValueError:
+            # in which case we'll simply post the line
+            pass
+        self.post = self.home.post(text = line)
+        self.post_cache[self.post.id] = self.post
+        self.undo.append("delete post %s" % self.post.id)
+        print("Posted. Use the 'show' command to show it.")
+
     def do_delete(self, line):
         """Delete a comment."""
         words = line.strip().split()
@@ -515,10 +558,12 @@ Use the 'edit' command to edit notes."""
         if notes:
             for n, note in enumerate(notes):
                 print(self.header("%2d. %s") % (n+1, note))
-            else:
-                print("Use 'edit' to create a note.")
+            print("Use the 'edit' command to edit a note.")
+            print("Use the 'preview' command to look at a note.")
+            print("Use the 'post' command to post a note.")
+            print("Use the 'comment' command to post a comment.")
         else:
-            print("Things to list: notes.")
+            print("Use 'edit' to create a note.")
 
     def get_notes(self):
         """Get the list of notes."""
@@ -528,6 +573,120 @@ Use the 'edit' command to edit notes."""
         """Get the correct path for a note."""
         return os.path.join(get_notes_dir(), filename)
 
+    def read_note(self, filename):
+        """Get text of a note."""
+        with open(self.get_note_path(filename), mode = 'r', encoding = 'utf-8') as fp:
+            return fp.read()
+
+    def do_preview(self, line):
+        """Preview a note using your pager.
+Use the 'pager' command to set your pager to something like 'mdcat'."""
+        if line == "":
+            print("The 'preview' command the number of a note as an argument.")
+            print("Use the 'notes' command to list all your notes.")
+            return
+        try:
+            n = int(line.strip())
+            notes = self.get_notes()
+            if notes:
+                try:
+                    self.show(self.read_note(notes[n-1]))
+                except IndexError:
+                    print("Use the 'list notes' command to list valid numbers.")
+                    return
+            else:
+                print("There are no notes to preview.")
+                return
+        except ValueError:
+            print("The 'preview' command takes a number as its argument.")
+            return
+
+    def do_home(self, line):
+        """Show the main stream containing the combined posts of the
+followed users and tags and the community spotlights posts if
+the user enabled those."""
+        if line == "":
+            if self.home:
+                print("Redisplaying the cached statuses of the home stream.")
+                print("Use the 'reload' argument to reload them.")
+                print("Use the 'all' argument to show them all.")
+                print("Use a number to show only that many.")
+                print("The default is 5.")
+            else:
+                print("Loading...")
+                self.home = diaspy.streams.Stream(self.connection)
+                self.home.fill()
+                for post in self.home:
+                    if post.id not in self.post_cache:
+                        self.post_cache[post.id] = post
+        elif line == "reload":
+            if self.connection == None:
+                print("Use the 'login' command, first.")
+                return
+            if self.home:
+                print("Reloading...")
+                self.home.update()
+                line = ""
+            else:
+                self.home = diaspy.streams.Stream(self.connection)
+                self.home.fill()
+
+        n = 5
+        posts = sorted(self.home, key=lambda x: x.data()["created_at"])
+
+        if line == "all":
+            n = None
+        elif line != "":
+            try:
+                n = int(line.strip())
+            except ValueError:
+                print("The 'home' command takes a number as its argument, or 'reload' or 'all'.")
+                print("The default is to show the last 5 posts.")
+                return
+
+        if n == None:
+            start = 0
+        else:
+            # n is from the back
+            start = max(len(posts) - n, 0)
+
+        if posts:
+            for n, post in enumerate(posts[start:], start):
+                print()
+                print(self.header("%2d. %s %s") % (n+1, post.data()["created_at"], post.author()))
+                print()
+                self.show(post)
+
+            print("Enter a number to select the post.")
+            self.numbers_refer_to = 'home'
+        else:
+            print("The people you follow have nothing to say.")
+            print("The tags you follow are empty. ðŸ˜¢")
+
+    def do_shortcuts(self, line):
+        """List all shortcuts."""
+        if line != "":
+            print("The 'shortcuts' command does not take an argument.")
+            return
+        print(self.header("Shortcuts"))
+        for shortcut in sorted(shortcuts):
+            print("%s\t%s" % (shortcut, shortcuts[shortcut]))
+        print("Use the 'shortcut' command to change or add shortcuts.")
+
+    def do_shortcut(self, line):
+        """Define a new shortcut."""
+        words = line.strip().split(maxsplit = 1)
+        if len(words) == 0:
+            return self.onecmd("shortcuts")
+        elif len(words) == 1:
+            shortcut = words[0]
+            if shortcut in shortcuts:
+                print("%s\t%s" % (shortcut, shortcuts[shortcut]))
+            else:
+                print("%s is not a shortcut" % shortcut)
+        else:
+            shortcuts[words[0]] = words[1]
+
 # Main function
 def main():