chiark / gitweb /
First cut at interleaving home timeline with mentions.
authorSimon Tatham <anakin@pobox.com>
Sat, 2 Dec 2023 18:59:50 +0000 (18:59 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 2 Dec 2023 18:59:50 +0000 (18:59 +0000)
It took so many tries to get that merge algorithm working that I
managed to get '429 Too many requests' from the server. I hope that's
sorted out tomorrow. Perhaps I should call it a night for now.

client.py
mastodonochrome

index 3e554348169d457e1c3a3458db8adfffda134b50..92569efbe6a0daf7ed752a99a95c51ee48acc020 100644 (file)
--- a/client.py
+++ b/client.py
@@ -88,7 +88,12 @@ class Client:
         params.setdefault('limit', 32)
         while True:
             links = {}
-            data = self.method(requests.get, path, base, params, links)
+            try:
+                data = self.method(requests.get, path, base, params, links)
+            except HTTPError as e:
+                if e.response.status_code == 429:
+                    # Blocked for too many requests, oops
+                    break
             yield from data
             if 'next' not in links:
                 break
index 823681daa32ea632f1bf1bb59a5020e57ab71b0c..4e50a518f2de02b34b94c4e6e57b7cd18cd3a5c9 100755 (executable)
@@ -5,6 +5,7 @@ Textual Mastodon client with a UI inspired by Monochrome BBS.
 '''
 
 import argparse
+import itertools
 import sys
 import unittest
 
@@ -12,26 +13,46 @@ import client
 import cursesclient
 import login
 
-class TimelineUI(client.Client):
-    def run(self):
-        for item in reversed(self.get("timelines/home", limit=20)):
-            p = client.Status(item)
-            for thing in p.text():
-                for line in thing.render(80):
-                    print(line.ecma48())
+class CombinedUI(client.Client):
+    def combined_feed(self):
+        feeds = [
+            ((item['created_at'], item)
+             for item in self.get_incremental("timelines/home")),
+            ((item['created_at'], item['status'])
+             for item in self.get_incremental("notifications")
+             if item['type'] == 'mention'),
+        ]
 
-class MentionsUI(client.Client):
-    def run(self):
-        g = self.get_incremental("notifications")
-        things = []
-        for item in g:
-            if item['type'] != 'mention':
-                continue
-            p = client.Status(item['status'])
-            things.append(p)
-            if len(things) >= 40:
+        items = []
+
+        nexts = [None for _ in feeds]
+
+        while True:
+            next_item = None
+
+            for i in range(len(feeds)):
+                if feeds[i] is not None and nexts[i] is None:
+                    try:
+                        nexts[i] = next(feeds[i])
+                    except StopIteration:
+                        feeds[i] = None
+                if nexts[i] is not None:
+                    if next_item is None:
+                        next_item = nexts[i]
+                    elif next_item[0] < nexts[i][0]:
+                        next_item = nexts[i]
+            if next_item is None:
                 break
-        for p in reversed(things):
+            yield next_item[1]
+            for i in range(len(feeds)):
+                if (nexts[i] is not None and
+                    nexts[i][1]['id'] == next_item[1]['id']):
+                    nexts[i] = None
+
+    def run(self):
+        items = list(itertools.islice(self.combined_feed(), 0, 100))
+        for item in reversed(items):
+            p = client.Status(item)
             for thing in p.text():
                 for line in thing.render(80):
                     print(line.ecma48())
@@ -51,12 +72,10 @@ def main():
     parser.add_argument("--log", help="File to log debug information to.")
     parser.add_argument("--test", nargs=argparse.REMAINDER,
                         help="Run unit tests.")
-    parser.add_argument("--timeline", action="store_const", dest="action",
-                        const=TimelineUI, help="Temporary mode to fetch "
-                        "the user's timeline and print it on the terminal.")
-    parser.add_argument("--mentions", action="store_const", dest="action",
-                        const=MentionsUI, help="Temporary mode to fetch "
-                        "the user's mentions and print them on the terminal.")
+    parser.add_argument("--combined", action="store_const", dest="action",
+                        const=CombinedUI, help="Temporary mode to fetch "
+                        "the user's timeline and mentions, interleave them, "
+                        "and print the result on the terminal.")
     parser.add_argument("--login", action="store_const", dest="action",
                         const=login.LoginUI, help="Log in to a user account.")
     parser.set_defaults(action=cursesclient.CursesUI)