def __init__(self, username, passhash): self.username = username self.passhash = passhash self.user_id = None self.rooms_by_id = {} self.rooms_by_name = {} self.sub_ids = {} self.client = DDPClient(RC_URL, debug=DEBUG) self.connected_handlers = {} self.changed_handlers = { "stream-room-messages": self.on_stream_room_messages } self.added_handlers = { "rocketchat_subscription": self.on_rocketchat_subscription, "stream-room-messages": self.on_stream_room_messages } self.client.on("connected", self.login) self.client.on("changed", self.on_changed) self.client.on("added", self.on_added) self.sent_messages = [] self.client.connect()
def _start_connect(self): up = parse.urlparse(settings.ROCKETCHAT_URL) if up.scheme == 'http': ws_proto = 'ws' else: ws_proto = 'wss' self.rc = DDPClient('{}://{}/websocket'.format(ws_proto, up.netloc), auto_reconnect=True, auto_reconnect_timeout=1) self.rc.on('connected', self._realtime_login) self.rc.on('changed', self._changed_callback) self.rc.connect()
def __init__(self, url, auto_reconnect=True, auto_reconnect_timeout=0.5, debug=False): EventEmitter.__init__(self) self.collection_data = CollectionData() self.ddp_client = DDPClient(url, auto_reconnect=auto_reconnect, auto_reconnect_timeout=auto_reconnect_timeout, debug=debug) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.on('reconnected', self._reconnected) self.connected = False self.subscriptions = {} self._login_data = None
class DDPCiaoClient: def __init__(self, ddp_params, ciao_queue): # validate params - START missing_params = [] required_params = ["host", "port"] for p in required_params: if not p in ddp_params: missing_params.append(p) if len(missing_params) > 0: raise RuntimeError("DDP configuration error, missing: %s" % ",".join(missing_params)) # validate params - END # reference to Queue for exchanging data with CiaoCore self.ciao_queue = ciao_queue # local instance of DDP Client self.handle = DDPClient("ws://" + ddp_params["host"] + ":" + str(ddp_params["port"]) + "/websocket") self.handle.on("connected", self.on_connect) self.host = ddp_params["host"] self.port = ddp_params["port"] self.logger = logging.getLogger("ddp.client") def on_connect(): self.logger.info("Connected to DDP Server %s" % self.host) """ TODO from METEOR to SKETCH def on_message(self, client, userdata, msg): self.logger.debug("Got new message. Topic: %s Message: %s" % (str(msg.topic), str(msg.payload))) entry = { "data" : [str(msg.topic), str(msg.payload)] } self.ciao_queue.put(entry) """ def connect(self): try: self.handle.connect() return True except: return False def disconnect(self): self.handle.close() def callback_function(a, b): self.logger.debug("Callback Function. Param1: %s Param2: %s" % (str(a), str(b))) def publish(self, function, parameter): self.logger.debug("Call function: %s Parameter: %s" % (function, str(parameter))) try: self.handle.call(function, json.loads(parameter), None) except Exception, e: self.logger.error("Failed handle call: " + str(e))
def __init__(self, ddp_endpoint): Cmd.__init__(self) self.ddp_client = DDPClient('ws://' + ddp_endpoint + '/websocket') self.subscriptions = {} self.pending_results = {} # Showing a fancy prompt string if we're interactive if sys.stdin.isatty(): self.prompt = ddp_endpoint + '> ' else: self.prompt = '' self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.connect()
def ddp_connect(self): print '++ DDP connection attempt...' self.ddp_client = DDPClient(self.server_url) self.ddp_client.debug = self.debug # Some events to handle from DDP self.ddp_client.on('added', self.added) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) # Connect to DDP server self.ddp_client.connect()
def __init__(self, ddp_endpoint): Cmd.__init__(self) self.ddp_client = DDPClient( 'ws://' + ddp_endpoint + '/websocket' ) self.subscriptions = {} self.pending_results = {} # Showing a fancy prompt string if we're interactive if sys.stdin.isatty(): self.prompt = ddp_endpoint + '> ' else: self.prompt = '' self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.connect()
def __init__(self, ddp_params, ciao_queue): # validate params - START missing_params = [] required_params = ["host", "port"] for p in required_params: if not p in ddp_params: missing_params.append(p) if len(missing_params) > 0: raise RuntimeError("DDP configuration error, missing: %s" % ",".join(missing_params)) # validate params - END # reference to Queue for exchanging data with CiaoCore self.ciao_queue = ciao_queue # local instance of DDP Client self.handle = DDPClient("ws://" + ddp_params["host"] + ":" + str(ddp_params["port"]) + "/websocket") self.handle.on("connected", self.on_connect) self.host = ddp_params["host"] self.port = ddp_params["port"] self.logger = logging.getLogger("ddp.client")
class App(Cmd): """Main input loop""" def __init__(self, ddp_endpoint): Cmd.__init__(self) self.ddp_client = DDPClient( 'ws://' + ddp_endpoint + '/websocket' ) self.subscriptions = {} self.pending_results = {} # Showing a fancy prompt string if we're interactive if sys.stdin.isatty(): self.prompt = ddp_endpoint + '> ' else: self.prompt = '' self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.connect() def subscribe(self, name, params): if name in self.subscriptions: self._log('* ALREADY SUBSCRIBED - {}'.format(name)) return sub_id = self.ddp_client.subscribe(name, params, self.subscribed) self.subscriptions[name] = sub_id def unsubscribe(self, name): if name not in self.subscriptions: self._log('* NOT SUBSCRIBED - {}'.format(name)) return self.ddp_client.unsubscribe(self.subscriptions[name]) del self.subscriptions[name] self._log('* UNSUBSCRIBED - {}'.format(name)) def call(self, method_name, params): self.ddp_client.call(method_name, params, self.result) # # Callbacks # def added(self, collection, id, fields): self._log('* ADDED {} {}'.format(collection, id), True) for key, value in fields.items(): self._log(' - FIELD {} {}'.format(key, value)) def changed(self, collection, id, fields, cleared): self._log('* CHANGED {} {}'.format(collection, id), True) for key, value in fields.items(): self._log(' - FIELD {} {}'.format(key, value)) for key, value in cleared.items(): self._log(' - CLEARED {} {}'.format(key, value)) def removed(self, collection, id): self._log('* REMOVED {} {}'.format(collection, id), True) def subscribed(self, error, sub_id): if error: self._remove_sub_by_id(sub_id) self._log('* SUBSCRIPTION FAILED - {}'.format(error.get('reason')), True) return self._log('* READY', True) def result(self, error, result): if error: self._log('* METHOD FAILED - {}'.format(error.get('reason')), True) return self._log('* METHOD RESULT - {}'.format(str(result)), True) def connected(self): self._log('* CONNECTED', True) def closed(self, code, reason): """Called when the connection is closed""" self._log('* CONNECTION CLOSED {} {}'.format(code, reason)) def failed(self, data): self._log('* FAILED - data: {}'.format(str(data))) # # Commands # def do_sub(self, params): """The `sub` command""" try: sub_name, sub_params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help sub`') return self.subscribe(sub_name, sub_params) def do_unsub(self, params): """The `unsub` command""" try: sub_name, sub_params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help unsub`') return self.unsubscribe(sub_name) def do_call(self, params): """The `call` command""" try: method, params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help call`') return self.call(method, params) def do_login(self, params): """The `login` command""" self._log('Not Implemented Yet -- SRP is in progress') self._log('However you can still calling the method `login` (WARNING: password sent in plaintext)') #self.ddp_client._login({'username': '******'}, 'password123') def do_EOF(self, line): """The `EOF` "command" It's here to support `cat file | python ddpclient.py` """ return True def do_help(self, line): """The `help` command""" msgs = { 'call': ( 'call <method name> <json array of parameters>\n' ' Calls a remote method\n' ' Example: call vote ["foo.meteor.com"]'), 'sub': ( 'sub <subscription name> [<json array of parameters>]\n' ' Subscribes to a remote dataset\n' ' Examples: `sub allApps` or `sub myApp ' '["foo.meteor.com"]`'), 'unsub': ( 'unsub <subscription name>\n' ' Unsubscribes from a remote dataset\n' ' Examples: `unsub allApps` or `unsub myApp ' '["foo.meteor.com"]`'), } line = line.strip() if line and line in msgs: return self._log('\n' + msgs[line]) for msg in msgs.values(): self._log('\n' + msg) # # Utils # def _log(self, msg, server_msg=False): if server_msg: sys.stderr.write('\n') sys.stderr.write('{}\n'.format(msg)) if server_msg: sys.stderr.write('{}'.format(self.prompt)) def _parse_command(self, params): """Parses a command with a first string param and a second json-encoded param""" name, args = (params + ' ').split(' ', 1) return name, args and json.loads(args) or [] def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name]
def main(url): character = {} client = DDPClient('ws://dicecloud.com/websocket', auto_reconnect=False) client.is_connected = False client.connect() def connected(): client.is_connected = True client.on('connected', connected) while not client.is_connected: time.sleep(1) client.subscribe('singleCharacter', [url]) def update_character(collection, _id, fields): if character.get(collection) is None: character[collection] = [] fields['id'] = _id character.get(collection).append(fields) client.on('added', update_character) time.sleep(10) client.close() character['id'] = url return character
class User: def __init__(self, username, passhash): self.username = username self.passhash = passhash self.user_id = None self.rooms_by_id = {} self.rooms_by_name = {} self.sub_ids = {} self.client = DDPClient(RC_URL, debug=DEBUG) self.connected_handlers = {} self.changed_handlers = { "stream-room-messages": self.on_stream_room_messages } self.added_handlers = { "rocketchat_subscription": self.on_rocketchat_subscription, "stream-room-messages": self.on_stream_room_messages } self.client.on("connected", self.login) self.client.on("changed", self.on_changed) self.client.on("added", self.on_added) self.sent_messages = [] self.client.connect() def login(self): self.client.call( "login", [{ "user": {"username": self.username}, "password": { "digest": self.passhash, "algorithm": "sha-256" } }], callback=self.login_callback ) def login_callback(self, u1, user_data): print(user_data) self.user_id = user_data["id"] self.sub_ids["subscription"] = self.client.subscribe("subscription", []) # self.join_room("GENERAL", "c") def join_room(self, room_id, room_type): self.sub_ids[room_id] = {} room = None while room is None: try: room = self.rooms_by_id[room_id] except KeyError: print(self.rooms_by_id) time.sleep(1) self.sub_ids[room_id]["room"] = self.client.subscribe("room", [room["type"]+room["name"]], callback=print) self.sub_ids[room_id]["stream-room-messages"] = self.client.subscribe("stream-room-messages", [room_id, True]) def on_stream_room_messages(self, collection, id, fields, *args): if "args" not in fields: return data = fields["args"][0] user = data["u"] if user["_id"] != self.user_id: return # print(data) if "t" in data and data["t"] == "rm": self.on_message_removed(data) elif "editedAt" in data: self.on_message_edited(data) elif data["msg"] != "": #ignore messages i sent from this client if data["_id"] in self.sent_messages: return self.on_message(data) else: print("Got unhandled message.") def on_changed(self, collection, id, fields, u1): if collection in self.changed_handlers: try: self.changed_handlers[collection](collection, id, fields, u1) except Exception as e: print("UNHANDLED EXCEPTION:", e) else: print("unhandled: ", collection, fields) def on_added(self, collection, id, fields): if collection in self.added_handlers: try: self.added_handlers[collection](collection, id, fields) except Exception as e: print("UNHANDLED EXCEPTION:", e) else: print("unhandled:", collection, fields) def on_rocketchat_subscription(self, collection, id, fields): data = fields room = { "name": data["name"], "rid": data["rid"], "type": data["t"] } self.rooms_by_id[room["rid"]] = room self.rooms_by_name[room["name"]] = room self.join_room(room["rid"], room["type"]) return def on_message_removed(self, data): print("My message was deleted!") print("The message %s was deleted by %s" % (data["_id"], data["editedBy"]["username"])) def on_message_edited(self, data): print("My message was edited!") print("The message %s was edited by %s and now reads '%s'" % (data["_id"], data["editedBy"]["username"], data["msg"])) def on_message(self, data): print("I sent a message!") print("'%s'" % data["msg"]) def send_message(self, room_id, msg): msg_id = make_id() print("attempting to send message with id '%s' and text '%s'" % (msg_id, msg)) self.sent_messages.append(msg_id) self.client.call( "sendMessage", [{ "_id": msg_id, "rid": room_id, "msg": msg }] )
class RocketChatBackend(IOBackend, StorageMixin): friendly_name = "RocketChat" internal_name = "will.backends.io_adapters.rocketchat" required_settings = [ { "name": "ROCKETCHAT_USERNAME", "obtain_at": """1. Go to your rocket.chat instance (i.e. your-name.rocket.chat) 2. Create a new normal account for Will. 3. Set this value to the username, just like you'd use to log in with it.""", }, { "name": "ROCKETCHAT_PASSWORD", "obtain_at": """1. Go to your rocket.chat instance (i.e. your-name.rocket.chat) 2. Create a new normal account for Will, and note the password you use. 3. Set this value to that password, just like you'd use to log in with it.""", }, { "name": "ROCKETCHAT_URL", "obtain_at": ("This is your rocket.chat url - typically either your-name.rocket.chat for " "Rocket.Chat cloud, or something like http://localhost:3000 for local installations." ), }, ] pp = pprint.PrettyPrinter(indent=4) def normalize_incoming_event(self, event): logging.info('Normalizing incoming Rocket.Chat event') logging.debug('event: {}'.format(self.pp.pformat(event))) if event["type"] == "message": # Were we mentioned? will_is_mentioned = False for mention in event['mentions']: if mention['username'] == self.me.handle: will_is_mentioned = True break # Handle direct messages, which in Rocket.Chat are a rid # made up of both users' _ids. is_private_chat = False if self.me.id in event["rid"]: is_private_chat = True # Create a "Channel" to align with Rocket.Chat DM # paradigm. There might well be a better way of doing # this. See TODO in _rest_channels_list. sender_id = event['u']['_id'] ids = [sender_id, self.me.id] ids.sort() channel_id = '{}{}'.format(*ids) sender = self.people[sender_id] channel_members = {} channel_members[sender_id] = sender channel_members[self.me.id] = self.me channel = Channel(id=channel_id, name=channel_id, source=clean_for_pickling(channel_id), members=channel_members) else: if "rid" in event and event["rid"] in self.channels: channel = clean_for_pickling(self.channels[event["rid"]]) else: # Private channel, unknown members. Just do our best and try to route it. if "rid" in event: channel = Channel(id=event["rid"], name=event["rid"], source=clean_for_pickling( event["rid"]), members={}) logging.debug('channel: {}'.format(channel)) # Set various variables depending on whether @handle was # part of the message. interpolated_handle = "@%s " % self.handle logging.debug( 'interpolated_handle: {}'.format(interpolated_handle)) is_direct = False if is_private_chat or event['msg'].startswith(interpolated_handle): is_direct = True # Strip my handle from the start. NB Won't strip it from # elsewhere in the text, and won't strip other mentions. # This will stop regexes from working, not sure if it's a # feature or a bug. if event['msg'].startswith(interpolated_handle): event['msg'] = event['msg'][len(interpolated_handle):].strip() if interpolated_handle in event['msg']: will_is_mentioned = True # Determine if Will said it. logging.debug('self.people: {}'.format(self.pp.pformat( self.people))) sender = self.people[event['u']['_id']] logging.debug('sender: {}'.format(sender)) if sender['handle'] == self.me.handle: logging.debug('Will said it') will_said_it = True else: logging.debug('Will didnt say it') will_said_it = False m = Message(content=event['msg'], type=event.type, is_direct=is_direct, is_private_chat=is_private_chat, is_group_chat=not is_private_chat, backend=self.internal_name, sender=sender, channel=channel, will_is_mentioned=will_is_mentioned, will_said_it=will_said_it, backend_supports_acl=True, original_incoming_event=clean_for_pickling(event)) return m else: logging.debug( 'Passing, I dont know how to normalize this event of type ', event["type"]) pass def handle_outgoing_event(self, event): # Print any replies. logging.info('Handling outgoing Rocket.Chat event') logging.debug('event: {}'.format(self.pp.pformat(event))) if event.type in ["say", "reply"]: if "kwargs" in event and "html" in event.kwargs and event.kwargs[ "html"]: event.content = html2text.html2text(event.content) self.send_message(event) if hasattr(event, "source_message") and event.source_message: pass else: # Backend needs to provide ways to handle and properly route: # 1. 1-1 messages # 2. Group (channel) messages # 3. Ad-hoc group messages (if they exist) # 4. Messages that have a channel/room explicitly specified that's different than # where they came from. # 5. Messages without a channel (Fallback to ROCKETCHAT_DEFAULT_CHANNEL) (messages that don't have a room ) kwargs = {} if "kwargs" in event: kwargs.update(**event.kwargs) if event.type in [ "topic_change", ]: self.set_topic(event.content) elif (event.type == "message.no_response" and event.data.is_direct and event.data.will_said_it is False): event.content = random.choice(UNSURE_REPLIES) self.send_message(event) def set_topic(self, event): logging.warn( "Rocket.Chat doesn't support topics yet: https://github.com/RocketChat/Rocket.Chat/issues/328" ) event.content( "Hm. Looks like Rocket.Chat doesn't support topics yet: https://github.com/RocketChat/Rocket.Chat/issues/328" ) self.send_message(event) def send_message(self, event): logging.info('Sending message to Rocket.Chat') logging.debug('event: {}'.format(self.pp.pformat(event))) data = {} if hasattr(event, "kwargs"): logging.debug('event.kwargs: {}'.format(event.kwargs)) data.update(event.kwargs) # TODO: Go through the possible attachment parameters at # https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage # - this is a bare minimum inspired by slack.py if 'color' in event.kwargs: data.update({ "attachments": [{ 'color': event.kwargs["color"], 'text': event.content, }], }) else: data.update({ 'text': event.content, }) else: # I haven't seen this yet, not sure when it's relevant. # 'text' was wrongly set to 'msg' and nothing blew up. ;) logging.debug("event doesn't have kwargs") data.update({ 'text': event.content, }) if "source_message" in event: if hasattr(event.source_message, "data"): data['roomId'] = event.source_message.data.channel.id else: data['roomId'] = event.source_message.channel.id else: data['roomId'] = event.data['source'].data.channel.id self._rest_post_message(data) def _get_rest_metadata(self): self._rest_users_list() self._rest_channels_list() def _get_realtime_metadata(self): self._realtime_get_rooms() # REST API functions, documented at # https://rocket.chat/docs/developer-guides/rest-api/ def _rest_login(self): params = { 'username': settings.ROCKETCHAT_USERNAME, 'password': settings.ROCKETCHAT_PASSWORD } r = requests.post('{}login'.format(self.rocketchat_api_url), data=params) resp_json = r.json() self._token = resp_json['data']['authToken'] self.save("WILL_ROCKETCHAT_TOKEN", self._token) self._userid = resp_json['data']['userId'] self.save("WILL_ROCKETCHAT_USERID", self._userid) def _rest_users_list(self): logging.debug('Getting users list from Rocket.Chat') # Remember to paginate. ;) count = 50 passes = 0 headers = {'X-Auth-Token': self.token, 'X-User-Id': self.userid} fetched = 0 total = 0 self.handle = settings.ROCKETCHAT_USERNAME self.mention_handle = "@%s" % settings.ROCKETCHAT_USERNAME people = {} while fetched <= total: params = {'count': count, 'offset': fetched} r = requests.get('{}users.list'.format(self.rocketchat_api_url), headers=headers, params=params) resp_json = r.json() if resp_json['success'] is False: logging.exception('resp_json: {}'.format(resp_json)) total = resp_json['total'] for user in resp_json['users']: # TODO: Unlike slack.py, no timezone support at present. # RC returns utcOffset, but this isn't enough to # determine timezone. # TODO: Pickle error if timezone set to UTC, and I didn't # have a chance to report it. Using GMT as a poor substitute. person = Person(id=user['_id'], handle=user['username'], mention_handle="@%s" % user["username"], source=clean_for_pickling(user)['username'], name=user['name'], timezone='GMT') people[user['_id']] = person if user['username'] == self.handle: self.me = person passes += 1 fetched = count * passes self.people = people def _get_userid_from_username(self, username): if username is None: raise TypeError("No username given") for id, data in self.people.items(): if data['handle'] == username: return id def _rest_channels_list(self): logging.debug('Getting channel list from Rocket.Chat') # Remember to paginate. ;) count = 50 passes = 0 headers = {'X-Auth-Token': self.token, 'X-User-Id': self.userid} fetched = 0 total = 0 channels = {} while fetched <= total: r = requests.get('{}channels.list'.format(self.rocketchat_api_url), headers=headers) resp_json = r.json() total = resp_json['total'] for channel in resp_json['channels']: members = {} for username in channel['usernames']: userid = self._get_userid_from_username(username) members[userid] = self.people[userid] channels[channel['_id']] = Channel( id=channel['_id'], name=channel['name'], source=clean_for_pickling(channel), members=members) passes += 1 fetched = count * passes self.channels = channels def _rest_post_message(self, data): logging.info('Posting message to Rocket.Chat REST API') logging.debug('data: {}'.format(data)) headers = {'X-Auth-Token': self.token, 'X-User-Id': self.userid} logging.debug('headers: {}'.format(headers)) r = requests.post( '{}chat.postMessage'.format(self.rocketchat_api_url), headers=headers, data=data, ) resp_json = r.json() # TODO: Necessary / useful to check return codes? if not 'success' in resp_json: logging.debug('resp_json: {}'.format(resp_json)) assert resp_json['success'] # Realtime API functions, documented at # https://rocket.chat/docs/developer-guides/realtime-api/ def _start_connect(self): up = parse.urlparse(settings.ROCKETCHAT_URL) if up.scheme == 'http': ws_proto = 'ws' else: ws_proto = 'wss' self.rc = DDPClient('{}://{}/websocket'.format(ws_proto, up.netloc), auto_reconnect=True, auto_reconnect_timeout=1) self.rc.on('connected', self._realtime_login) self.rc.on('changed', self._changed_callback) self.rc.connect() def _realtime_login(self): params = [{ 'user': { 'username': settings.ROCKETCHAT_USERNAME }, 'password': settings.ROCKETCHAT_PASSWORD }] self.rc.call('login', params, self._login_callback) def _login_callback(self, error, result): logging.debug('_login_callback') if error: logging.exception('error: {}'.format(error)) return logging.debug('result: {}'.format(result)) logging.debug('self.token: {}'.format(self.token)) logging.debug('self.userid: {}'.format(self.userid)) # Use dummy to make it a Thread, otherwise DDP events don't # get back to the right place. If there is a real need to make # it a real Process, it is probably just a matter of using # multiprocessing.Value(s) in the right place(s). # TODO: Could this be the reason for the 100% CPU usage? # Have asked in #development. self.update_thread = Process(target=self._get_updates) self.update_thread.start() def _changed_callback(self, collection, _id, fields, cleared): logging.debug('_changed_callback') logging.debug('collection: {}'.format(collection)) logging.debug('id: {}'.format(_id)) logging.debug('fields: {}'.format(self.pp.pformat(fields))) logging.debug('cleared: {}'.format(cleared)) event = Event(type='message', version=1, **fields['args'][0]) self.handle_incoming_event(event) def _stream_room_message_callback(self, error, event): logging.debug('_stream_room_message_callback') if error: logging.exception('error: {}'.format(error)) return @property def token(self): if not hasattr(self, "_token") or not self._token: self._token = self.load("WILL_ROCKETCHAT_TOKEN", None) if not self._token: self._rest_login() return self._token @property def userid(self): if not hasattr(self, "_userid") or not self._userid: self._userid = self.load("WILL_ROCKETCHAT_USERID", None) if not self._userid: self._rest_login() return self._userid @property def rocketchat_api_url(self): if settings.ROCKETCHAT_URL.endswith("/"): return settings.ROCKETCHAT_URL + 'api/v1/' else: return settings.ROCKETCHAT_URL + '/api/v1/' # Gets updates from REST and Realtime APIs. def _get_updates(self): try: polling_interval_seconds = 5 self._get_rest_metadata() while True: # Update channels/people/me/etc. self._get_rest_metadata() self._get_realtime_metadata() time.sleep(polling_interval_seconds) except (KeyboardInterrupt, SystemExit): pass except: logging.critical("Error in watching RocketChat API: \n%s" % traceback.format_exc()) # Use this to get a list of all rooms that we are in. # https://rocket.chat/docs/developer-guides/realtime-api/the-room-object def _realtime_get_rooms(self): params = [{'$date': 0}] self.rc.call('rooms/get', params, self._get_rooms_callback) def _get_rooms_callback(self, error, result): logging.debug('_get_rooms_callback') if error: logging.exception('_get_rooms_callback error: {}'.format(error)) return # TODO: When we leave a room, we don't delete it from # self.subscribed_rooms. Not a problem in practice - # subscriptions to the room won't fire, but messy. for room in result: logging.debug('room: {}'.format(room)) if room['_id'] not in self.subscribed_rooms: self.rc.subscribe('stream-room-messages', [room['_id']], self._stream_room_message_callback) self.subscribed_rooms[room['_id']] = True def bootstrap(self): # Bootstrap must provide a way to to have: # a) self.normalize_incoming_event fired, or incoming events put into self.incoming_queue # b) any necessary threads running for a) # c) self.me (Person) defined, with Will's info # d) self.people (dict of People) defined, with everyone in an organization/backend # e) self.channels (dict of Channels) defined, with all available channels/rooms. # Note that Channel asks for users, a list of People. # f) A way for self.handle, self.me, self.people, and self.channels to be kept accurate, # with a maximum lag of 60 seconds. self.subscribed_rooms = {} # Gets and stores token and ID. self._rest_login() # Kicks off listeners and REST room polling. self._start_connect()
from DDPClient import DDPClient import time from random import randint client = DDPClient("ws://vestee.herokuapp.com/websocket") client.connect() while True: # api = "a72cc00489d7acda461724ae01ab2d" api = "01a66de93638c40cd87786cf2c9647" client.call('add_data', [api, 1, 'Temperatura zewnetrzna', '*C', randint(20,30)], []) client.call('add_data', [api, 2, 'Temperatura piwka', '*C', randint(10, 20)], []) client.call('add_data', [api, 3, 'Wilgotnosc', '%', randint(20,80)], []) time.sleep(2)
class MeteorClient(EventEmitter): def __init__(self, url, auto_reconnect=True, auto_reconnect_timeout=0.5, debug=False, headers=None): EventEmitter.__init__(self) self.collection_data = CollectionData() self.ddp_client = DDPClient(url, auto_reconnect=auto_reconnect, auto_reconnect_timeout=auto_reconnect_timeout, debug=debug, headers=headers) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.on('reconnected', self._reconnected) self.connected = False self.subscriptions = {} self._login_data = None self._login_token = None def connect(self): """Connect to the meteor server""" self.ddp_client.connect() def close(self): """Close connection with meteor server""" self.ddp_client.close() def _reconnected(self): """Reconnect Currently we get a new session every time so we have to clear all the data an resubscribe""" if self._login_data or self._login_token: def reconnect_login_callback(error, result): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=reconnect_login_callback) return else: raise MeteorClientException( 'Failed to re-authenticate during reconnect') self.connected = True self._resubscribe() if self._login_token: self._resume(self._login_token, callback=reconnect_login_callback) else: self._login(self._login_data, callback=reconnect_login_callback) else: self._resubscribe() def _resubscribe(self): self.collection_data.data = {} cur_subs = self.subscriptions.items() self.subscriptions = {} for name, value in cur_subs: self.subscribe(name, value['params']) self.emit('reconnected') # # Account Management # def login(self, user, password, token=None, callback=None): """Login with a username and password Arguments: user - username or email address password - the password for the account Keyword Arguments: token - meteor resume token callback - callback function containing error as first argument and login data""" # TODO: keep the tokenExpires around so we know the next time # we need to authenticate # hash the password hashed = hashlib.sha256(password).hexdigest() # handle username or email address if '@' in user: user_object = { 'email': user } else: user_object = { 'username': user } password_object = { 'algorithm': 'sha-256', 'digest': hashed } self._login_token = token self._login_data = {'user': user_object, 'password': password_object} if token: self._resume(token, callback=callback) else: self._login(self._login_data, callback=callback) def _resume(self, token, callback=None): login_data = {'resume': token} self._login(login_data, callback=callback) def _login(self, login_data, callback=None): self.emit('logging_in') def logged_in(error, data): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=callback) return if callback: callback(error, None) return self._login_token = data['token'] if callback: callback(None, data) self.emit('logged_in', data) self.ddp_client.call('login', [login_data], callback=logged_in) def logout(self, callback=None): """Logout a user Keyword Arguments: callback - callback function called when the user has been logged out""" self.ddp_client.call('logout', [], callback=callback) self.emit('logged_out') # # Meteor Method Call # def call(self, method, params, callback=None): """Call a remote method Arguments: method - remote method name params - remote method parameters Keyword Arguments: callback - callback function containing return data""" self._wait_for_connect() self.ddp_client.call(method, params, callback=callback) # # Subscription Management # def subscribe(self, name, params=[], callback=None): """Subscribe to a collection Arguments: name - the name of the publication params - the subscription parameters Keyword Arguments: callback - a function callback that returns an error (if exists)""" self._wait_for_connect() def subscribed(error, sub_id): if error: self._remove_sub_by_id(sub_id) if callback: callback(error.get('reason')) return if callback: callback(None) self.emit('subscribed', name) if name in self.subscriptions: raise MeteorClientException('Already subcribed to {}'.format(name)) sub_id = self.ddp_client.subscribe(name, params, subscribed) self.subscriptions[name] = { 'id': sub_id, 'params': params } def unsubscribe(self, name): """Unsubscribe from a collection Arguments: name - the name of the publication""" self._wait_for_connect() if name not in self.subscriptions: raise MeteorClientException('No subscription for {}'.format(name)) self.ddp_client.unsubscribe(self.subscriptions[name]['id']) del self.subscriptions[name] self.emit('unsubscribed', name) # # Collection Management # def find(self, collection, selector={}): """Find data in a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns all items in a collection)""" results = [] for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: results.append(doc) for key, value in selector.items(): if key in doc and doc[key] == value: results.append(doc) return results def find_one(self, collection, selector={}): """Return one item from a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns first item found)""" for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: return doc for key, value in selector.items(): if key in doc and doc[key] == value: return doc return None def insert(self, collection, doc, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified doc - The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the _id as the second.""" self.call("/" + collection + "/insert", [doc], callback=callback) def update(self, collection, selector, modifier, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified selector - specifies which documents to modify modifier - Specifies how to modify the documents Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second.""" self.call("/" + collection + "/update", [selector, modifier], callback=callback) def remove(self, collection, selector, callback=None): """Remove an item from a collection Arguments: collection - the collection to be modified selector - Specifies which documents to remove Keyword Arguments: callback - Optional. If present, called with an error object as its argument.""" self.call("/" + collection + "/remove", [selector], callback=callback) # # Event Handlers # def connected(self): self.connected = True self.emit('connected') def closed(self, code, reason): self.connected = False self.emit('closed', code, reason) def failed(self, data): self.emit('failed', str(data)) def added(self, collection, id, fields): self.collection_data.add_data(collection, id, fields) self.emit('added', collection, id, fields) def changed(self, collection, id, fields, cleared): self.collection_data.change_data(collection, id, fields, cleared) self.emit('changed', collection, id, fields, cleared) def removed(self, collection, id): self.collection_data.remove_data(collection, id) self.emit('removed', collection, id) # # Helper functions # def _time_from_start(self, start): now = datetime.datetime.now() return now - start def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name] def _wait_for_connect(self): start = datetime.datetime.now() while not self.connected and self._time_from_start(start).seconds < 5: time.sleep(0.1) if not self.connected: raise MeteorClientException('Could not subscribe because a connection has not been established')
class User: def __init__(self, username, passhash): self.username = username self.passhash = passhash self.user_id = None self.rooms_by_id = {} self.rooms_by_name = {} self.sub_ids = {} self.client = DDPClient(RC_URL, debug=DEBUG) self.connected_handlers = {} self.changed_handlers = { "stream-room-messages": self.on_stream_room_messages } self.added_handlers = { "rocketchat_subscription": self.on_rocketchat_subscription, "stream-room-messages": self.on_stream_room_messages } self.client.on("connected", self.login) self.client.on("changed", self.on_changed) self.client.on("added", self.on_added) self.sent_messages = [] self.client.connect() def login(self): self.client.call("login", [{ "user": { "username": self.username }, "password": { "digest": self.passhash, "algorithm": "sha-256" } }], callback=self.login_callback) def login_callback(self, u1, user_data): print(user_data) self.user_id = user_data["id"] self.sub_ids["subscription"] = self.client.subscribe( "subscription", []) # self.join_room("GENERAL", "c") def join_room(self, room_id, room_type): self.sub_ids[room_id] = {} room = None while room is None: try: room = self.rooms_by_id[room_id] except KeyError: print(self.rooms_by_id) time.sleep(1) self.sub_ids[room_id]["room"] = self.client.subscribe( "room", [room["type"] + room["name"]], callback=print) self.sub_ids[room_id]["stream-room-messages"] = self.client.subscribe( "stream-room-messages", [room_id, True]) def on_stream_room_messages(self, collection, id, fields, *args): if "args" not in fields: return data = fields["args"][0] user = data["u"] if user["_id"] != self.user_id: return # print(data) if "t" in data and data["t"] == "rm": self.on_message_removed(data) elif "editedAt" in data: self.on_message_edited(data) elif data["msg"] != "": #ignore messages i sent from this client if data["_id"] in self.sent_messages: return self.on_message(data) else: print("Got unhandled message.") def on_changed(self, collection, id, fields, u1): if collection in self.changed_handlers: try: self.changed_handlers[collection](collection, id, fields, u1) except Exception as e: print("UNHANDLED EXCEPTION:", e) else: print("unhandled: ", collection, fields) def on_added(self, collection, id, fields): if collection in self.added_handlers: try: self.added_handlers[collection](collection, id, fields) except Exception as e: print("UNHANDLED EXCEPTION:", e) else: print("unhandled:", collection, fields) def on_rocketchat_subscription(self, collection, id, fields): data = fields room = {"name": data["name"], "rid": data["rid"], "type": data["t"]} self.rooms_by_id[room["rid"]] = room self.rooms_by_name[room["name"]] = room self.join_room(room["rid"], room["type"]) return def on_message_removed(self, data): print("My message was deleted!") print("The message %s was deleted by %s" % (data["_id"], data["editedBy"]["username"])) def on_message_edited(self, data): print("My message was edited!") print("The message %s was edited by %s and now reads '%s'" % (data["_id"], data["editedBy"]["username"], data["msg"])) def on_message(self, data): print("I sent a message!") print("'%s'" % data["msg"]) def send_message(self, room_id, msg): msg_id = make_id() print("attempting to send message with id '%s' and text '%s'" % (msg_id, msg)) self.sent_messages.append(msg_id) self.client.call("sendMessage", [{ "_id": msg_id, "rid": room_id, "msg": msg }])
class App(Cmd): """Main input loop""" def __init__(self, ddp_endpoint): Cmd.__init__(self) self.ddp_client = DDPClient('ws://' + ddp_endpoint + '/websocket') self.subscriptions = {} self.pending_results = {} # Showing a fancy prompt string if we're interactive if sys.stdin.isatty(): self.prompt = ddp_endpoint + '> ' else: self.prompt = '' self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.connect() def subscribe(self, name, params): if name in self.subscriptions: self._log('* ALREADY SUBSCRIBED - {}'.format(name)) return sub_id = self.ddp_client.subscribe(name, params, self.subscribed) self.subscriptions[name] = sub_id def unsubscribe(self, name): if name not in self.subscriptions: self._log('* NOT SUBSCRIBED - {}'.format(name)) return self.ddp_client.unsubscribe(self.subscriptions[name]) del self.subscriptions[name] self._log('* UNSUBSCRIBED - {}'.format(name)) def call(self, method_name, params): self.ddp_client.call(method_name, params, self.result) # # Callbacks # def added(self, collection, id, fields): self._log('* ADDED {} {}'.format(collection, id), True) for key, value in fields.items(): self._log(' - FIELD {} {}'.format(key, value)) def changed(self, collection, id, fields, cleared): self._log('* CHANGED {} {}'.format(collection, id), True) for key, value in fields.items(): self._log(' - FIELD {} {}'.format(key, value)) for key, value in cleared.items(): self._log(' - CLEARED {} {}'.format(key, value)) def removed(self, collection, id): self._log('* REMOVED {} {}'.format(collection, id), True) def subscribed(self, error, sub_id): if error: self._remove_sub_by_id(sub_id) self._log('* SUBSCRIPTION FAILED - {}'.format(error.get('reason')), True) return self._log('* READY', True) def result(self, error, result): if error: self._log('* METHOD FAILED - {}'.format(error.get('reason')), True) return self._log('* METHOD RESULT - {}'.format(str(result)), True) def connected(self): self._log('* CONNECTED', True) def closed(self, code, reason): """Called when the connection is closed""" self._log('* CONNECTION CLOSED {} {}'.format(code, reason)) def failed(self, data): self._log('* FAILED - data: {}'.format(str(data))) # # Commands # def do_sub(self, params): """The `sub` command""" try: sub_name, sub_params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help sub`') return self.subscribe(sub_name, sub_params) def do_unsub(self, params): """The `unsub` command""" try: sub_name, sub_params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help unsub`') return self.unsubscribe(sub_name) def do_call(self, params): """The `call` command""" try: method, params = self._parse_command(params) except ValueError: self._log('Error parsing parameter list - try `help call`') return self.call(method, params) def do_login(self, params): """The `login` command""" self._log('Not Implemented Yet -- SRP is in progress') self._log( 'However you can still calling the method `login` (WARNING: password sent in plaintext)' ) #self.ddp_client._login({'username': '******'}, 'password123') def do_EOF(self, line): """The `EOF` "command" It's here to support `cat file | python ddpclient.py` """ return True def do_help(self, line): """The `help` command""" msgs = { 'call': ('call <method name> <json array of parameters>\n' ' Calls a remote method\n' ' Example: call vote ["foo.meteor.com"]'), 'sub': ('sub <subscription name> [<json array of parameters>]\n' ' Subscribes to a remote dataset\n' ' Examples: `sub allApps` or `sub myApp ' '["foo.meteor.com"]`'), 'unsub': ('unsub <subscription name>\n' ' Unsubscribes from a remote dataset\n' ' Examples: `unsub allApps` or `unsub myApp ' '["foo.meteor.com"]`'), } line = line.strip() if line and line in msgs: return self._log('\n' + msgs[line]) for msg in msgs.values(): self._log('\n' + msg) # # Utils # def _log(self, msg, server_msg=False): if server_msg: sys.stderr.write('\n') sys.stderr.write('{}\n'.format(msg)) if server_msg: sys.stderr.write('{}'.format(self.prompt)) def _parse_command(self, params): """Parses a command with a first string param and a second json-encoded param""" name, args = (params + ' ').split(' ', 1) return name, args and json.loads(args) or [] def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name]
class MeteorClient(EventEmitter): def __init__(self, url, auto_reconnect=True, auto_reconnect_timeout=0.5, debug=False): EventEmitter.__init__(self) self.collection_data = CollectionData() self.ddp_client = DDPClient( url, auto_reconnect=auto_reconnect, auto_reconnect_timeout=auto_reconnect_timeout, debug=debug) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) self.ddp_client.on('added', self.added) self.ddp_client.on('changed', self.changed) self.ddp_client.on('removed', self.removed) self.ddp_client.on('reconnected', self._reconnected) self.connected = False self.subscriptions = {} self._login_data = None self._login_token = None def connect(self): """Connect to the meteor server""" self.ddp_client.connect() def close(self): """Close connection with meteor server""" self.ddp_client.close() def _reconnected(self): """Reconnect Currently we get a new session every time so we have to clear all the data an resubscribe""" if self._login_data or self._login_token: def reconnect_login_callback(error, result): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=reconnect_login_callback) return else: raise MeteorClientException( 'Failed to re-authenticate during reconnect') self.connected = True self._resubscribe() if self._login_token: self._resume(self._login_token, callback=reconnect_login_callback) else: self._login(self._login_data, callback=reconnect_login_callback) else: self._resubscribe() def _resubscribe(self): self.collection_data.data = {} cur_subs = self.subscriptions.items() self.subscriptions = {} for name, value in cur_subs: self.subscribe(name, value['params']) self.emit('reconnected') # # Account Management # def login(self, user, password, token=None, callback=None): """Login with a username and password Arguments: user - username or email address password - the password for the account Keyword Arguments: token - meteor resume token callback - callback function containing error as first argument and login data""" # TODO: keep the tokenExpires around so we know the next time # we need to authenticate # hash the password hashed = hashlib.sha256(password).hexdigest() # handle username or email address if '@' in user: user_object = {'email': user} else: user_object = {'username': user} password_object = {'algorithm': 'sha-256', 'digest': hashed} self._login_token = token self._login_data = {'user': user_object, 'password': password_object} if token: self._resume(token, callback=callback) else: self._login(self._login_data, callback=callback) def _resume(self, token, callback=None): login_data = {'resume': token} self._login(login_data, callback=callback) def _login(self, login_data, callback=None): self.emit('logging_in') def logged_in(error, data): if error: if self._login_token: self._login_token = None self._login(self._login_data, callback=callback) return if callback: callback(error, None) return self._login_token = data['token'] if callback: callback(None, data) self.emit('logged_in', data) self.ddp_client.call('login', [login_data], callback=logged_in) def logout(self, callback=None): """Logout a user Keyword Arguments: callback - callback function called when the user has been logged out""" self.ddp_client.call('logout', [], callback=callback) self.emit('logged_out') # # Meteor Method Call # def call(self, method, params, callback=None): """Call a remote method Arguments: method - remote method name params - remote method parameters Keyword Arguments: callback - callback function containing return data""" self._wait_for_connect() self.ddp_client.call(method, params, callback=callback) # # Subscription Management # def subscribe(self, name, params=[], callback=None): """Subscribe to a collection Arguments: name - the name of the publication params - the subscription parameters Keyword Arguments: callback - a function callback that returns an error (if exists)""" self._wait_for_connect() def subscribed(error, sub_id): if error: self._remove_sub_by_id(sub_id) if callback: callback(error.get('reason')) return if callback: callback(None) self.emit('subscribed', name) if name in self.subscriptions: raise MeteorClientException('Already subcribed to {}'.format(name)) sub_id = self.ddp_client.subscribe(name, params, subscribed) self.subscriptions[name] = {'id': sub_id, 'params': params} def unsubscribe(self, name): """Unsubscribe from a collection Arguments: name - the name of the publication""" self._wait_for_connect() if name not in self.subscriptions: raise MeteorClientException('No subscription for {}'.format(name)) self.ddp_client.unsubscribe(self.subscriptions[name]['id']) del self.subscriptions[name] self.emit('unsubscribed', name) # # Collection Management # def find(self, collection, selector={}): """Find data in a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns all items in a collection)""" results = [] for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: results.append(doc) for key, value in selector.items(): if key in doc and doc[key] == value: results.append(doc) return results def find_one(self, collection, selector={}): """Return one item from a collection Arguments: collection - collection to search Keyword Arguments: selector - the query (default returns first item found)""" for _id, doc in self.collection_data.data.get(collection, {}).items(): doc.update({'_id': _id}) if selector == {}: return doc for key, value in selector.items(): if key in doc and doc[key] == value: return doc return None def insert(self, collection, doc, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified doc - The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you. Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the _id as the second.""" self.call("/" + collection + "/insert", [doc], callback=callback) def update(self, collection, selector, modifier, callback=None): """Insert an item into a collection Arguments: collection - the collection to be modified selector - specifies which documents to modify modifier - Specifies how to modify the documents Keyword Arguments: callback - Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second.""" self.call("/" + collection + "/update", [selector, modifier], callback=callback) def remove(self, collection, selector, callback=None): """Remove an item from a collection Arguments: collection - the collection to be modified selector - Specifies which documents to remove Keyword Arguments: callback - Optional. If present, called with an error object as its argument.""" self.call("/" + collection + "/remove", [selector], callback=callback) # # Event Handlers # def connected(self): self.connected = True self.emit('connected') def closed(self, code, reason): self.connected = False self.emit('closed', code, reason) def failed(self, data): self.emit('failed', str(data)) def added(self, collection, id, fields): self.collection_data.add_data(collection, id, fields) self.emit('added', collection, id, fields) def changed(self, collection, id, fields, cleared): self.collection_data.change_data(collection, id, fields, cleared) self.emit('changed', collection, id, fields, cleared) def removed(self, collection, id): self.collection_data.remove_data(collection, id) self.emit('removed', collection, id) # # Helper functions # def _time_from_start(self, start): now = datetime.datetime.now() return now - start def _remove_sub_by_id(self, sub_id): for name, cur_sub_id in self.subscriptions.items(): if cur_sub_id == sub_id: del self.subscriptions[name] def _wait_for_connect(self): start = datetime.datetime.now() while not self.connected and self._time_from_start(start).seconds < 5: time.sleep(0.1) if not self.connected: raise MeteorClientException( 'Could not subscribe because a connection has not been established' )
class Hoduino(): """ A listener for the arduino """ latest_hash = None def __init__(self, ddp_server_url, debug=False): self.shutting_down = False self.server_url = ddp_server_url self.debug = debug self.board_interface = HoduinoBoardInterface(port=ARDUINO_PORT) # Setup connection to the DDP server self.ddp_connect() def ddp_connect(self): print '++ DDP connection attempt...' self.ddp_client = DDPClient(self.server_url) self.ddp_client.debug = self.debug # Some events to handle from DDP self.ddp_client.on('added', self.added) self.ddp_client.on('connected', self.connected) self.ddp_client.on('socket_closed', self.closed) self.ddp_client.on('failed', self.failed) # Connect to DDP server self.ddp_client.connect() def exit(self): self.shutting_down = True # Close connect to Arduino if self.board_interface: self.board_interface.close_arduino() # Close connection to self.ddp_client.close() def connected(self): print '++ DDP Connected' self.ddp_connected = True # Subscribe to messages stream. Limit messages to 1 as we # only want the latest message. sub_id = self.ddp_client.subscribe('messages', [{'limit': 1}]) def closed(self, code, reason): print '++ DDP connection closed {0} {1}'.format(code, reason) self.ddp_connected = False if not self.shutting_down: while not self.ddp_connected: self.ddp_connect() time.sleep(30) def failed(self, data): print '++ DDP failed - data: {0}'.format(str(data)) def added(self, collection, id, fields): print '++ DDP added {0} {1}'.format(len(collection), id) if id <> self.latest_hash: print '++ New messages!' self.board_interface.donation_reaction() self.latest_hash = id else: print '++ No new messages'