Messing with TT-RSS in Python

I use the feed reader TT-RSS to read RSS/ATOM feeds. It’s a good reader, and I also use TTRSS-Reader for Android (there are others, which I’ve not tried yet) on my (ancient) Android mobile device to quickly go through incoming feed items. But I have some itches to the web interface that I am considering scratching, so I decided to see how hard it is to start building a simpler interface.

One of the things I knew from setting up the Android application is that TT-RSS has an API, which is where I started. It communicates via JSON, which is great because it is ubiquitous and easy to use.

You have to have a session to talk to the API, so I built a class to handle all requests and the session:

class TTRSSSession():
    def __init__(self, url, user, password):
        self.session_id = None
        self.url = url
        self.user = user
        self.password = password
        self._login()

    def headlines(self, feed):
        data = {
            "feed_id": feed,
            "limit": 10,
            #"skip": 11,
        }
        resp = self._request('getHeadlines', data)
        return resp

    def _login(self):
        login_data = {
            "user": self.user,
            "password": self.password,
        }
        result = self._request("login", login_data)
        self.session_id = result['session_id']

    def _request(self, op, data):
        request_data = {
            "op": op,
        }
        if not self.session_id and op != "login":
            raise OpException("Tried to execute without a session/login.")
            return None
        elif self.session_id and op != "login":
            request_data['sid'] = self.session_id

        request_data = dict_join((request_data, data))
        request_data_j = json.dumps(request_data).encode('utf-8')
        response = urllib.request.urlopen(self.url, data=request_data_j)
        r_data_j = response.read()
        encoding = chardet.detect(r_data_j)['encoding']
        r_data_j = r_data_j.decode(encoding, 'replace')
        r_data = json.loads(r_data_j)
        if r_data['status'] != 0:
            raise OpException(
                "Bad request: {0}".format(r_data['content']['error']))
            return None
        # FIXME handling multiple parts (seq = #)?
        return r_data['content']

That’s the main guts. This isn’t very advanced/functional yet, but it’s enough for me to mess with. I also built a very simple Flask application that uses this class so I can begin sketching some UI I might want (though for now it’s very bare bones, just showing the result of TTRSSSession.headlines()).

I was going to use Django, but I think it’s too heavy for what I want out of it. Flask is very light.

A sample invocation of the class:

feeds = {
    'FRESH': -3,
    'STARRED': -1,
    'ALL': -4,
}
url = 'http://example.com/tt-rss/api/'
user = 'random_hacker'
passw = 'xyzzy'
ttr = TTRSSSession(url, user, passw)
ttr.headlines(feeds['ALL'])

The headlines() method will return the first ten items in the feed containing all items (a special feed that throws everything read and unread together).

As you can see in the class’ _request() method, I haven’t added support for pulling multiple calls together. That will likely require a JavaScript frontend to integrate with my Flask application (or the data would all get pulled by the backend first, which seems wrong). And that begs the question of why not to forgo Python altogether and just write this as a pure client-side JavaScript thing?

Maybe I will. For now I am just messing around. Depending, I might prefer to use a Python backend to help filter or manage some of the items automatically. So at this point the versatility seems useful. Also, I always feel slightly silly when I find myself doing something that feels too close to rewriting part of an application that already (and still will) have that part, so who knows. I may just use this to learn more about Flask.