source: python/wa.py @ 2b4402f

Last change on this file since 2b4402f was 2b4402f, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-20T12:56:47Z

Fixed bugs by not doing things according to the docs but according to the
example implementation.

Contact list handling works better now. It figures out which contacts are
valid and which ones are not, etc. Also, status/presence is starting to
work though for some reason yowsup is not giving me the incoming presence
stanzas. Yeah no idea why. \o/

  • Property mode set to 100755
File size: 8.8 KB
Line 
1#!/usr/bin/python
2
3import logging
4import threading
5import time
6
7import yowsup
8
9from yowsup.layers.auth                        import YowAuthenticationProtocolLayer
10from yowsup.layers.protocol_acks               import YowAckProtocolLayer
11from yowsup.layers.protocol_chatstate          import YowChatstateProtocolLayer
12from yowsup.layers.protocol_contacts           import YowContactsIqProtocolLayer
13from yowsup.layers.protocol_groups             import YowGroupsProtocolLayer
14from yowsup.layers.protocol_ib                 import YowIbProtocolLayer
15from yowsup.layers.protocol_iq                 import YowIqProtocolLayer
16from yowsup.layers.protocol_messages           import YowMessagesProtocolLayer
17from yowsup.layers.protocol_notifications      import YowNotificationsProtocolLayer
18from yowsup.layers.protocol_presence           import YowPresenceProtocolLayer
19from yowsup.layers.protocol_privacy            import YowPrivacyProtocolLayer
20from yowsup.layers.protocol_profiles           import YowProfilesProtocolLayer
21from yowsup.layers.protocol_receipts           import YowReceiptProtocolLayer
22from yowsup.layers.network                     import YowNetworkLayer
23from yowsup.layers.coder                       import YowCoderLayer
24from yowsup.stacks import YowStack, YowStackBuilder
25from yowsup.common import YowConstants
26from yowsup.layers import YowLayerEvent
27from yowsup.stacks import YowStack, YOWSUP_CORE_LAYERS
28from yowsup import env
29
30from yowsup.layers.interface                             import YowInterfaceLayer, ProtocolEntityCallback
31from yowsup.layers.protocol_acks.protocolentities        import *
32from yowsup.layers.protocol_chatstate.protocolentities   import *
33from yowsup.layers.protocol_contacts.protocolentities    import *
34from yowsup.layers.protocol_groups.protocolentities      import *
35from yowsup.layers.protocol_ib.protocolentities          import *
36from yowsup.layers.protocol_iq.protocolentities          import *
37from yowsup.layers.protocol_media.mediauploader import MediaUploader
38from yowsup.layers.protocol_media.protocolentities       import *
39from yowsup.layers.protocol_messages.protocolentities    import *
40from yowsup.layers.protocol_notifications.protocolentities import *
41from yowsup.layers.protocol_presence.protocolentities    import *
42from yowsup.layers.protocol_privacy.protocolentities     import *
43from yowsup.layers.protocol_profiles.protocolentities    import *
44from yowsup.layers.protocol_receipts.protocolentities    import *
45from yowsup.layers.axolotl.protocolentities.iq_key_get import GetKeysIqProtocolEntity
46from yowsup.layers.axolotl import YowAxolotlLayer
47from yowsup.common.tools import ModuleTools
48
49import implugin
50
51logger = logging.getLogger("yowsup.layers.network.layer")
52logger.setLevel(logging.DEBUG)
53ch = logging.StreamHandler()
54ch.setLevel(logging.DEBUG)
55logger.addHandler(ch)
56
57class BitlBeeLayer(YowInterfaceLayer):
58
59        def __init__(self, *a, **kwa):
60                super(BitlBeeLayer, self).__init__(*a, **kwa)
61
62        def receive(self, entity):
63                print "Received: %r" % entity
64                print entity
65                super(BitlBeeLayer, self).receive(entity)
66
67        def Ship(self, entity):
68                """Send an entity into Yowsup, but through the correct thread."""
69                print "Queueing: %s" % entity.getTag()
70                print entity
71                def doit():
72                        self.toLower(entity)
73                self.getStack().execDetached(doit)
74
75        @ProtocolEntityCallback("success")
76        def onSuccess(self, entity):
77                self.b = self.getStack().getProp("org.bitlbee.Bijtje")
78                self.cb = self.b.bee
79                self.b.yow = self
80                self.cb.connected()
81                self.toLower(AvailablePresenceProtocolEntity())
82       
83        @ProtocolEntityCallback("failure")
84        def onFailure(self, entity):
85                self.b = self.getStack().getProp("org.bitlbee.Bijtje")
86                self.cb = self.b.bee
87                self.cb.error(entity.getReason())
88                self.cb.logout(False)
89
90        def onEvent(self, event):
91                print event
92                if event.getName() == "disconnect":
93                        self.getStack().execDetached(self.daemon.StopDaemon)
94       
95        @ProtocolEntityCallback("presence")
96        def onPresence(self, pres):
97                status = 8 # MOBILE
98                online = isinstance(pres, AvailablePresenceProtocolEntity)
99                if online:
100                        status += 1 # ONLINE
101                imcb_buddy_status(pres.getFrom(), status, None, None)
102       
103        @ProtocolEntityCallback("message")
104        def onMessage(self, msg):
105                self.cb.buddy_msg(msg.getFrom(), msg.getBody(), 0, msg.getTimestamp())
106
107                receipt = OutgoingReceiptProtocolEntity(msg.getId(), msg.getFrom())
108                self.toLower(receipt)
109
110        @ProtocolEntityCallback("receipt")
111        def onReceipt(self, entity):
112                print "ACK THE ACK!"
113                ack = OutgoingAckProtocolEntity(entity.getId(), entity.getTag(),
114                                                entity.getType(), entity.getFrom())
115                self.toLower(ack)
116
117        @ProtocolEntityCallback("iq")
118        def onIq(self, entity):
119                if isinstance(entity, ResultSyncIqProtocolEntity):
120                        return self.onSyncResult(entity)
121       
122        def onSyncResult(self, entity):
123                # TODO HERE AND ELSEWHERE: Threat idiocy happens when going
124                # from here to the IMPlugin. Check how bjsonrpc lets me solve that.
125                ok = set(num.lstrip("+") for num in entity.inNumbers)
126                for handle in self.b.contacts:
127                        if handle.split("@")[0] in ok:
128                                self.toLower(SubscribePresenceProtocolEntity(handle))
129                                self.cb.add_buddy(handle, "")
130                if entity.outNumbers:
131                        self.cb.error("Not on WhatsApp: %s" % ", ".join(entity.outNumbers))
132                if entity.invalidNumbers:
133                        self.cb.error("Invalid numbers: %s" % ", ".join(entity.invalidNumbers))
134
135        @ProtocolEntityCallback("notification")
136        def onNotification(self, ent):
137                if isinstance(ent, StatusNotificationProtocolEntity):
138                        return self.onStatusNotification(ent)
139       
140        def onStatusNotification(self, status):
141                print "New status for %s: %s" % (status.getFrom(), status.status)
142
143        @ProtocolEntityCallback("chatstate")
144        def onChatstate(self, entity):
145                print(entity)
146
147
148class YowsupDaemon(threading.Thread):
149        daemon = True
150        stack = None
151
152        class Terminate(Exception):
153                pass
154
155        def run(self):
156                try:
157                        self.stack.loop(timeout=0.2, discrete=0.2, count=1)
158                except YowsupDaemon.Terminate:
159                        print "Exiting loop!"
160                        pass
161       
162        def StopDaemon(self):
163                # Ugly, but yowsup offers no "run single iteration" version
164                # of their event loop :-(
165                raise YowsupDaemon.Terminate
166
167
168class YowsupIMPlugin(implugin.BitlBeeIMPlugin):
169        NAME = "wa"
170        SETTINGS = {
171                "cc": {
172                        "type": "int",
173                },
174                "name": {
175                        "flags": 0x100, # NULL_OK
176                },
177        }
178        AWAY_STATES = ["Available"]
179        ACCOUNT_FLAGS = 14 # HANDLE_DOMAINS + STATUS_MESSAGE + LOCAL_CONTACTS
180        # TODO: LOCAL LIST CAUSES CRASH!
181        # TODO: HANDLE_DOMAIN in right place (add ... ... nick bug)
182        # TODO? Allow set_away (for status msg) even if AWAY_STATES not set?
183        #   and/or, see why with the current value set_away state is None.
184
185        def login(self, account):
186                self.stack = self.build_stack(account)
187                self.daemon = YowsupDaemon(name="yowsup")
188                self.daemon.stack = self.stack
189                self.daemon.start()
190                self.bee.log("Started yowsup thread")
191               
192                self.logging_in = True
193                self.contacts = set()
194
195        def keepalive(self):
196                self.yow.Ship(PingIqProtocolEntity(to="s.whatsapp.net"))
197
198        def logout(self):
199                self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT))
200                self.stack.execDetached(self.daemon.StopDaemon)
201
202        def buddy_msg(self, to, text, flags):
203                msg = TextMessageProtocolEntity(text, to=to)
204                self.yow.Ship(msg)
205
206        def add_buddy(self, handle, _group):
207                if self.logging_in:
208                        # Need to batch up the initial adds. This is a "little" ugly.
209                        self.contacts.add(handle)
210                else:
211                        self.yow.Ship(GetSyncIqProtocolEntity(
212                            ["+" + handle.split("@")[0]], mode=GetSyncIqProtocolEntity.MODE_DELTA))
213                        self.yow.Ship(SubscribePresenceProtocolEntity(handle))
214
215        def remove_buddy(self, handle, _group):
216                self.yow.Ship(UnsubscribePresenceProtocolEntity(handle))
217
218        def set_away(self, _state, status):
219                # When our first status is set, we've finalised login.
220                # Which means sync the full contact list now.
221                if self.logging_in:
222                        self.logging_in = False
223                        self.send_initial_contacts()
224               
225                # I think state is not supported?
226                print "Trying to set status to %r, %r" % (_state, status)
227                self.yow.Ship(SetStatusIqProtocolEntity(status))
228
229        def send_initial_contacts(self):
230                if not self.contacts:
231                        return
232                numbers = [("+" + x.split("@")[0]) for x in self.contacts]
233                self.yow.Ship(GetSyncIqProtocolEntity(numbers))
234
235        def set_set_name(self, _key, value):
236                self.yow.Ship(PresenceProtocolEntity(name=value))
237
238        def build_stack(self, account):
239                creds = (account["user"].split("@")[0], account["pass"])
240
241                stack = (YowStackBuilder()
242                         .pushDefaultLayers(False)
243                         .push(BitlBeeLayer)
244                         .build())
245                stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, creds)
246                stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])
247                stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
248                stack.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource())
249                stack.setProp("org.bitlbee.Bijtje", self)
250
251                stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
252
253                return stack
254
255implugin.RunPlugin(YowsupIMPlugin, debug=True)
Note: See TracBrowser for help on using the repository browser.