source: python/implugin.py @ 199d51c

Last change on this file since 199d51c was f8feb8a, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-07T10:33:06Z

add_buddy and remove_buddy should no longer be mandatory.

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