source: python/implugin.py @ b20014b

Last change on this file since b20014b was b20014b, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-09T12:17:54Z

Store the hex_version coming from BitlBee as a tuple instead of raw, that's
a little nicer.

  • Property mode set to 100755
File size: 5.6 KB
RevLine 
[f3af614]1#!/usr/bin/python
2
3import sys
4import bjsonrpc
5from bjsonrpc.handlers import BaseHandler
6
[e9face7]7import operator
[f3af614]8import random
9import re
10import socket
11import time
12
[e9face7]13import requests
[2533d10]14
[f3af614]15# List of functions an IM plugin can export. This library will indicate to
16# BitlBee which functions are actually implemented so omitted features
17# will be disabled, but note that some/many functions are simply mandatory.
[f8feb8a]18# (Currently login/-out, buddy_msg.)
[f3af614]19SUPPORTED_FUNCTIONS = [
20        'login', 'keepalive', 'logout', 'buddy_msg', 'set_away',
21        'send_typing', 'add_buddy', 'remove_buddy', 'add_permit',
22        'add_deny', 'rem_permit', 'rem_deny', 'get_info', 'chat_invite',
23        'chat_kick', 'chat_leave', 'chat_msg', 'chat_with', 'chat_join',
24        'chat_topic'
25]
26
[b20014b]27def make_version_tuple(hex):
28        """Convert the BitlBee binary-encoded version number into something
29        more "Pythonic". Could use distutils.version instead but its main
30        benefit appears to be string parsing which here is not that useful."""
31
32        return (hex >> 16, (hex >> 8) & 0xff, hex & 0xff)
33
[f3af614]34class RpcForwarder(object):
35        """Tiny object that forwards RPCs from local Python code to BitlBee
36        with a marginally nicer syntax. This layer could eventually be
37        used to add basic parameter checking though I don't think that should
38        be done here."""
39       
40        def __init__(self, methods, target):
41                for m in methods:
42                        # imc(b)_ prefix is not useful here, chop it.
43                        # (Maybe do this in BitlBee already as well.)
44                        newname = re.sub("^imcb?_", "", m)
45                        self.__setattr__(newname, target.__getattr__(m))
46
47class BitlBeeIMPlugin(BaseHandler):
[c5a7b8d]48        # Protocol name to be used in the BitlBee CLI, etc.
[f3af614]49        NAME = "rpc-test"
[c5a7b8d]50
51        # See account.h (TODO: Add constants.)
[e9face7]52        ACCOUNT_FLAGS = 0
[c5a7b8d]53       
54        # Supported away states. If your protocol supports a specific set of
55        # away states, put them in a list in this variable.
[2533d10]56        AWAY_STATES = None
[f3af614]57       
58        # Filled in during initialisation:
[b20014b]59        # Version number as a three-tuple, so 3.2 becomes (3, 2, 0).
[f3af614]60        bitlbee_version = None
61        # Full version string
62        bitlbee_version_str = None
[c5a7b8d]63        # Will become an RpcForwarder object to call into BitlBee
64        bee = None
[f3af614]65       
[e9face7]66        BASE_URL = "https://newsblur.com"
67       
[f3af614]68        @classmethod
69        def _factory(cls, *args, **kwargs):
70                def handler_factory(connection):
71                        handler = cls(connection, *args, **kwargs)
72                        return handler
73                return handler_factory
74       
75        #def __init__(self, connection, *args, **kwargs):
76        #       BaseHandler.__init__(self,connection)
77
[e9face7]78        def url(self, path):
79                return (self.BASE_URL + path)
80
[f3af614]81        def init(self, bee):
82                self.bee = RpcForwarder(bee["method_list"], self._conn.call)
[b20014b]83                self.bitlbee_version = make_version_tuple(bee["version"])
[f3af614]84                self.bitlbee_version_str = bee["version_str"]
[2533d10]85
[f3af614]86                # TODO: See how to call into the module here.
87                return {
88                        "name": self.NAME,
89                        "method_list": list(set(dir(self)) & set(SUPPORTED_FUNCTIONS)),
[c5a7b8d]90                        "account_flags": self.ACCOUNT_FLAGS,
91                        "away_state_list": self.AWAY_STATES,
[f3af614]92                        "settings": {
93                                "oauth": {
94                                        "default": "off",
[f15553d]95                                        "type": "bool",
[f3af614]96                                },
97                                "test": {
98                                        "default": "123",
[f15553d]99                                        "type": "int",
[f3af614]100                                },
[f15553d]101                                "stringetje": {
102                                        "default": "testje",
103                                        "flags": 0x04,
104                                }
[f3af614]105                        },
106                }
107       
108        def login(self, account):
[e9face7]109                self.ua = requests.Session()
110                creds = {"username": account["user"], "password": account["pass"]}
111                r = self.ua.post(self.url("/api/login"), creds)
[b20014b]112                self.bee.log("You're running BitlBee %d.%d.%d" % self.bitlbee_version)
[e9face7]113                if r.status_code != 200:
114                        self.bee.error("HTTP error %d" % r.status_code)
115                        self.bee.logout(True)
116                elif r.json()["errors"]:
117                        self.bee.error("Authentication error")
118                        self.bee.logout(False)
119                else:
120                        self.bee.add_buddy("rss", None)
121                        self.bee.connected()
122                        self.seen_hashes = set()
123                        self.keepalive()
[f3af614]124
125        def logout(self):
126                self.bee.error("Ok bye!")
127
[2533d10]128        def buddy_msg(self, handle, msg, flags):
129                feed = self.feeds[handle]
130                cmd = re.split(r"\s+", msg)
[f15553d]131       
132        def set_set(self, setting, value):
133                print "Setting %s changed to %r" % (setting, value)
[e9face7]134       
135        # BitlBee will call us here every minute which is actually a neat way
136        # to get periodic work (like RSS polling) scheduled. :-D
137        def keepalive(self):
138                r = self.ua.post(
139                        self.url("/reader/unread_story_hashes"),
140                        {"include_timestamps": True})
141                if r.status_code != 200:
142                        self.bee.error("HTTP error %d" % r.status_code)
143                        return
144
145                # Throw all unread-post hashes in a long list and sort it by posting time.
146                #feed_hashes = r.json()["unread_feed_story_hashes"]
147                wtf = r.json()
148                feed_hashes = wtf["unread_feed_story_hashes"]
149                all_hashes = []
150                for feed, hashes in feed_hashes.iteritems():
151                        all_hashes += [tuple(h) for h in hashes]
152                all_hashes.sort(key=operator.itemgetter(1))
153               
154                # Look at the most recent 20, grab the ones we haven't shown yet.
155                req_hashes = []
156                for hash, _ in all_hashes[-20:]:
157                        if hash not in self.seen_hashes:
158                                req_hashes.append(hash)
159               
160                if not req_hashes:
161                        return
162                print req_hashes
163               
164                # Grab post details.
165                r = self.ua.post(self.url("/reader/river_stories"), {"h": req_hashes})
166                if r.status_code != 200:
167                        self.bee.error("HTTP error %d" % r.status_code)
168                        return
169               
170                # Response is not in the order we requested. :-(
171                wtf = r.json()
172                stories = {}
173                for s in wtf["stories"]:
174                        stories[s["story_hash"]] = s
175               
176                for s in (stories[hash] for hash in req_hashes):
177                        line = "%(story_title)s <%(story_permalink)s>" % s
178                        ts = int(s.get("story_timestamp", "0"))
179                        self.bee.buddy_msg("rss", line, 0, ts)
180                        self.seen_hashes.add(s["story_hash"])
181                        print s["story_hash"]
182
[f3af614]183
184def RunPlugin(plugin, debug=True):
185        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
186        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
187        sock.bind("/tmp/rpcplugins/test2")
188        sock.listen(3)
189       
190        srv = bjsonrpc.server.Server(sock, plugin._factory())
191       
192        srv.debug_socket(debug)
193        srv.serve()
194
195RunPlugin(BitlBeeIMPlugin)
Note: See TracBrowser for help on using the repository browser.