Esempio n. 1
0
class gdax_websocket:
    _current = {}
    _ws = {}

    def on_message(self, ws, message):
        msg = loads(message)
        if 'product_id' in msg:
            self._current[msg['product_id']] = msg

    def on_open(self, socket):
        params = {
            "type":
            "subscribe",
            "channels": [{
                "name":
                "ticker",
                "product_ids": ["BTC-USD", "ETH-USD", "LTC-USD", "BCH-USD"]
            }]
        }
        socket.send(dumps(params))

    def start(self):
        url = "wss://ws-feed.gdax.com"
        self._ws = WebSocketApp(url,
                                on_open=self.on_open,
                                on_message=self.on_message)
        thread = threading.Thread(target=self._ws.run_forever)
        thread.start()

    def stop(self):
        self._ws.close()

    def get(self, product_id, key):
        return self._current[product_id][key]
Esempio n. 2
0
class GDAXWebsocket:
    """
    Stream product data
    Store in memory until time frame met then write to DB
    """
    def __init__(self):
        self.__ws = None

    @staticmethod
    def __handle_error(ws, error):
        print(f'WS Error: {error}')

    def __open_websocket(self, subscribe_data: dict, process_data_func: Callable):
        """

        :param subscribe_data: Channel subscribe data
        :param process_data_func: function that takes two arguments (ws, message)
        :return:
        """

        def __on_open(ws):
            ws.send(data=json.dumps(subscribe_data))

        self.__ws = WebSocketApp(WEBSOCKET_URL, on_message=process_data_func, on_error=self.__handle_error)
        self.__ws.on_open = __on_open
        self.__ws.run_forever()

    def close_websocket(self):
        self.__ws.close()

    def ticker_channel_socket(self, process_data_func: Callable, product_ids = List[str]):
        """
        Example Response
        [{
            "type": "ticker",
            "trade_id": 20153558,
            "sequence": 3262786978,
            "time": "2017-09-02T17:05:49.250000Z",
            "product_id": "BTC-USD",
            "price": "4388.01000000",
            "side": "buy", // Taker side
            "last_size": "0.03000000",
            "best_bid": "4388",
            "best_ask": "4388.01"
        },]
        :param process_data_func: function to process data on socket receive
        :param product_ids: List of string product ids: ['ETH-USD', 'ETH-BTC']
        :return:
        """
        print('Creating Socket')
        subscribe_data = {
            'type': 'subscribe',
            'product_ids': product_ids,
            'channels': [{'name': 'ticker', 'product_ids': product_ids}]
        }
        self.__open_websocket(subscribe_data, process_data_func=process_data_func)
        return self.__ws
Esempio n. 3
0
class WebsocketClient:
    ''' Websocket Client ラッパークラス '''
    def __init__(self):
        self.client = None
        self.logger = create_logger()

    def open(self, server_host):
        ''' server_hostにクライアントを新規接続 '''
        self.close()
        self.client = create_connection(server_host)

    def open_server(self, server_host):
        ''' server_hostにクライアントを新規接続し、サーバーとして動作させる '''
        self.close()
        enableTrace(False)
        self.client = WebSocketApp(
            server_host,
            on_message=lambda ws, message: self.on_message_recieved(message),
            on_error=lambda ws, error: self.on_error(error),
            on_close=lambda ws: self.on_disconnect())
        self.client.on_open = lambda ws: self.on_connect()
        self.client.run_forever()

    def close(self):
        ''' clientをサーバーから切断する '''
        if self.client is not None:
            self.client.close()
            self.client = None

    def log(self, msg, *args, **kargs):
        ''' ログメッセージを出力 '''
        self.logger.info(msg, *args, **kargs)

    def send_message(self, message):
        ''' 接続先のサーバーにmessage送信 '''
        return self.client.send(message)

    def recieve_message(self):
        ''' 接続先サーバーから送られてきたメッセージを受信 '''
        return self.client.recv()

    # --- event methods (サーバーモードで動作する際に使用) --- #
    def on_connect(self):
        ''' Websocketサーバー接続時のコールバック '''
        self.log('Connect')

    def on_disconnect(self):
        ''' Websocketサーバーから切断時のコールバック '''
        self.log('Disconnect')

    def on_message_recieved(self, message):
        ''' Websocketサーバーからメッセージ受信時のコールバック '''
        self.log('Received:{}'.format(message))

    def on_error(self, error):
        ''' エラー発生時のコールバック '''
        self.log('Error:{}'.format(error))
class WebSocketService():

    def __init__( self, url, onMessage ):
        
        def runWS( service ):
            service._wsapp.run_forever()
        
        self._wsapp = WebSocketApp( url, on_message=onMessage )
        thread.start_new_thread( runWS, ( self, ) )
    
    def stop( self ):
        self._wsapp.close()
Esempio n. 5
0
    def _on_error(self, ws: websocket.WebSocketApp, error: Exception):
        """ Callback method to react on errors. We will try reconnecting with prolonging intervals. """
        self._logger.exception(error)
        self._connected = False
        self._reachable = False
        ws.close()
        self._event_sequence = 0

        sleep_interval = 16
        while not self._reachable:
            self._try_reconnect(sleep_interval)
            sleep_interval = sleep_interval * 2 if sleep_interval < 2048 else 3600

        self.websocket_connect()
Esempio n. 6
0
class UpbitReal:
    def __init__(self, request, callback=print):
        self.request = request
        self.callback = callback
        self.ws = WebSocketApp(
            url="wss://api.upbit.com/websocket/v1",
            on_message=lambda ws, msg: self.on_message(ws, msg),
            on_error=lambda ws, msg: self.on_error(ws, msg),
            on_close=lambda ws: self.on_close(ws),
            on_open=lambda ws: self.on_open(ws))
        self.running = False

    def on_message(self, ws, msg):
        msg = json.loads(msg.decode('utf-8'))
        # result = dict()
        now = datetime.datetime.now()
        # result['time'] = msg['tdt'] + msg['ttm']    -> UTC
        # result['time'] = str(now)
        # result['cur_price'] = msg['tp']
        # result['acc_volume'] = msg['atv']
        # result = json.dumps(result)
        # print(type(result))
        conn.hmset("bitData", {
            "time": str(now),
            "curPrice": msg['tp'],
            "accVolume": msg['atv']
        })
        # self.callback(msg)

    def on_error(self, ws, msg):
        self.callback(msg)

    def on_close(self, ws):
        self.callback("closed")
        self.running = False

    def on_open(self, ws):
        th = Thread(target=self.activate, daemon=True)
        th.start()

    def activate(self):
        self.ws.send(self.request)
        while self.running:
            time.sleep(1)
        self.ws.close()

    def start(self):
        self.running = True
        self.ws.run_forever()
Esempio n. 7
0
class Firebase(Thread):

    def __init__(self):
        super(Firebase, self).__init__()
        self.sock = WebSocketApp(url='ws://%s.ws' % BASE_URL,
                                 on_message=self.msg_handler,
                                 on_open=self.connect_handler,
                                 on_error=self.err_handler)
        self.ready = Event()
        self._rn = 0

    def run(self):
        self.sock.run_forever()

    def connect_handler(self, sock):
        sock.send(u'version=1')

    def err_handler(self, exc):
        print exc

    def send(self, msg):
        self.sock.send(msg)

    @property
    def rn(self):
        self._rn = self._rn + 1
        return self._rn

    def listen_to_channel(self, channel):
        print 'listening to %s' % channel
        self.send('1')
        data = {
            "rn": "%i" % self.rn,
            "payload": {
                "action": "listen",
                "path": channel
            }
        }
        self.send(json.dumps(data))

    def close(self):
        self.sock.close()
Esempio n. 8
0
class UpbitWebSocket:
    def __init__(self, request, callback=print):
        self.request = request
        self.callback = callback
        self.ws = WebSocketApp(
            url="wss://api.upbit.com/websocket/v1",
            on_message=lambda ws, msg: self.on_message(ws, msg),
            on_error=lambda ws, msg: self.on_error(ws, msg),
            on_close=lambda ws:     self.on_close(ws),
            on_open=lambda ws:     self.on_open(ws))
        self.running = False
    
    def on_message(self, ws, msg):
        msg = json.loads(msg.decode('utf-8'))
        self.callback(msg)
    
    def on_error(self, ws, msg):
        self.callback(msg)
    
    def on_close(self, ws):
        self.callback("closed")
        self.running = False

    def on_open(self, ws):
        th = Thread(target=self.activate, daemon=True)
        th.start()
    
    def activate(self):
        self.ws.send(self.request)
        while self.running:
            time.sleep(1)
        self.ws.close()
    
    def start(self):
        self.running = True
        self.ws.run_forever()
Esempio n. 9
0
class WifiClient:
    """
    Class to manage entire wifi client

    Usage:
        >>> client = WifiClient()  # Starts client
        >>> client.join()
    """
    def __init__(self, allow_timeout=True):
        self.allow_timeout = allow_timeout
        self.running = False

        self.last_lease_mod = self.get_last_lease_mod()
        self.wiface = pyw.winterfaces()[0]
        self.ap = AccessPoint(self.wiface)
        self.client = WebSocketApp(url=config.websocket['url'],
                                   on_message=self.on_message)
        Thread(target=self.client.run_forever).start()
        self.run_thread = Thread(target=self.run, daemon=True)

        self.server = None
        try:
            self.server = WebServer(self.ap.ip, 80)
            self.server.start()
        except RuntimeError:
            self.close()
            raise

        self.run_thread.start()

    def get_last_lease_mod(self):
        """When a new user connects, the lease modification time changes"""
        return os.path.getmtime('/var/lib/misc/dnsmasq.leases')

    def join(self):
        """Waits for wifi setup to complete"""
        try:
            self.run_thread.join()
        except:
            LOG.exception('Error in wifi thread:')
            self.close()

    def notify_server(self, name, data=None):
        """Send a message to javascript"""
        self.client.send(json.dumps({'type': name, 'data': data or {}}))

    def on_message(self, _, message: str):
        """Handle communication from javascript"""
        message = json.loads(message)

        def invalid_message(**_):
            pass

        # Javascript events
        {
            'wifi.cancel': self.cancel,
            'wifi.stop': self.close,
            'wifi.scan': self.scan,
            'wifi.connect': self.connect
        }.get(message['type'], invalid_message)(**message.get('data', {}))

    def run(self):
        """
        Fire up the MYCROFT access point for the user to connect to
        with a phone or computer.
        """
        try:
            self.monitor_connection()
        except:
            LOG.exception('Error in wifi client:')
            self.close()

    def monitor_connection(self):
        trigger_event('ap_up')
        has_connected = False
        num_failures = 0
        start_time = time.time()
        self.running = True

        while self.running:
            # do our monitoring...
            mod_time = self.get_last_lease_mod()
            if self.last_lease_mod != mod_time:
                # Something changed in the dnsmasq lease file -
                # presumably a (re)new lease
                if not has_connected:
                    trigger_event('ap_device_connected')
                has_connected = True
                num_failures = 0
                self.last_lease_mod = mod_time
                start_time = time.time()  # reset start time after connection

            if time.time() - start_time > 60 * 5 and self.allow_timeout:
                # After 5 minutes, shut down the access point (unless the
                # system has never been setup, in which case we stay up
                # indefinitely)
                LOG.info("Auto-shutdown of access point after 5 minutes")
                self.cancel()
                continue

            if has_connected:
                # Flush the ARP entries associated with our access point
                # This will require all network hardware to re-register
                # with the ARP tables if still present.
                if num_failures == 0:
                    cli_no_output('ip', '-s', '-s', 'neigh', 'flush',
                                  self.ap.subnet + '.0/24')

                # now look at the hardware that has responded, if no entry
                # shows up on our access point after 2*5=10 seconds, the user
                # has disconnected
                if not self.is_ARP_filled():
                    num_failures += 1
                    LOG.info('Lost connection: ' + str(num_failures))
                    if num_failures > 5:
                        trigger_event('ap_device_disconnected')
                        has_connected = False
                else:
                    num_failures = 0
            sleep(5)  # wait a bit to prevent thread from hogging CPU

    def is_ARP_filled(self):
        out = cli_no_output('/usr/sbin/arp', '-n')["stdout"]
        if not out:
            return False
        # Parse output, skipping header
        for o in out.split("\n")[1:]:
            if o.startswith(self.ap.subnet):
                if "(incomplete)" in o:
                    # ping the IP to get the ARP table entry reloaded
                    ip_disconnected = o.split(" ")[0]
                    cli_no_output('/bin/ping', '-c', '1', '-W', '3',
                                  ip_disconnected)
                else:
                    return True  # something on subnet is connected!
        return False

    def scan(self):
        trigger_event('ap_scan')
        LOG.info("Scanning wifi connections...")
        networks = {}
        status = self.get_connection_info()

        for cell in Cell.all(self.wiface):
            if "x00" in cell.ssid:
                continue  # ignore hidden networks

            # Fix UTF-8 characters
            ssid = literal_eval("b'" + cell.ssid + "'").decode('utf8')
            quality = self.get_quality(cell.quality)

            # If there are duplicate network IDs (e.g. repeaters) only
            # report the strongest signal
            update = True
            if ssid in networks:
                update = networks[ssid]["quality"] < quality
            if update and ssid:
                networks[ssid] = {
                    'quality': quality,
                    'encrypted': cell.encrypted,
                    'connected': self.is_connected(ssid, status),
                    'demo': False
                }
        LOG.info("Found wifi networks: %s" % networks)
        self.notify_server('wifi.scanned', {'networks': networks})

    @staticmethod
    def get_quality(quality):
        values = quality.split("/")
        return float(values[0]) / float(values[1])

    def connect(self, ssid, password=None):
        LOG.info('Connecting to ' + ssid + '...')
        connected = self.is_connected(ssid)

        if connected:
            LOG.warning("Device is already connected to %s" % ssid)
        else:
            self.disconnect()
            LOG.info("Connecting to: %s" % ssid)
            nid = wpa(self.wiface, 'add_network')
            wpa(self.wiface, 'set_network', nid, 'ssid', '"' + ssid + '"')

            if password:
                psk = '"' + password + '"'
                wpa(self.wiface, 'set_network', nid, 'psk', psk)
            else:
                wpa(self.wiface, 'set_network', nid, 'key_mgmt', 'NONE')

            wpa(self.wiface, 'enable', nid)
            connected = self.get_connected(ssid)
            if connected:
                wpa(self.wiface, 'save_config')

        trigger_event(
            'ap_connection_success' if connected else 'ap_connection_failed')
        self.notify_server('connection.status', {'connected': connected})
        LOG.info("Connection status for %s = %s" % (ssid, connected))

    def disconnect(self):
        """Disconnect from current SSID"""
        status = self.get_connection_info()
        nid = status.get("id")
        if nid:
            ssid = status.get("ssid")
            wpa(self.wiface, 'disable', nid)
            LOG.info("Disconnecting %s id: %s" % (ssid, nid))

    def get_connection_info(self):
        res = cli('wpa_cli', '-i', self.wiface, 'status')
        out = str(res["stdout"])
        if out:
            return dict(o.split("=") for o in out.split("\n")[:-1])
        return {}

    def get_connected(self, ssid, retry=5):
        connected = self.is_connected(ssid)
        while not connected and retry > 0:
            sleep(1)
            retry -= 1
            connected = self.is_connected(ssid)
        return connected

    def is_connected(self, ssid, status=None):
        status = status or self.get_connection_info()
        state = status.get("wpa_state")
        return status.get("ssid") == ssid and state == "COMPLETED"

    def cancel(self):
        trigger_event('ap_cancel')
        self.close()

    def close(self):
        trigger_event('ap_down')
        self.running = False
        LOG.info('Shutting down access point...')
        self.ap.close()
        LOG.info('Sending shutdown signal...')
        if self.server:
            self.server.shutdown()
        LOG.info('Closing websocket...')
        self.client.close()
        LOG.info("Wifi client stopped!")
Esempio n. 10
0
class MidiHandler:
    # Initializes the handler class
    def __init__(self,
                 config_path="config.json",
                 ws_server="localhost",
                 ws_port=4444):
        # Setting up logging first and foremost
        self.log = get_logger("midi_to_obs")

        # Internal service variables
        self._action_buffer = []
        self._action_counter = 2
        self._portobjects = []

        #load tinydb configuration database
        self.log.debug("Trying to load config file  from %s" % config_path)
        tiny_database = TinyDB(config_path, indent=4)
        tiny_db = tiny_database.table("keys", cache_size=20)
        tiny_devdb = tiny_database.table("devices", cache_size=20)

        #get all mappings and devices
        self._mappings = tiny_db.all()
        self._devices = tiny_devdb.all()

        #open dbj datebase for mapping and clear
        self.mappingdb = dbj("temp-mappingdb.json")
        self.mappingdb.clear()

        #convert database to dbj in-memory
        for _mapping in self._mappings:
            self.mappingdb.insert(_mapping)

        self.log.debug("Mapping database: `%s`" % str(self.mappingdb.getall()))

        if len(self.mappingdb.getall()) < 1:
            self.log.critical("Could not cache device mappings")
            # ENOENT (No such file or directory)
            exit(2)

        self.log.debug("Successfully imported mapping database")

        result = tiny_devdb.all()
        if not result:
            self.log.critical("Config file %s doesn't exist or is damaged" %
                              config_path)
            # ENOENT (No such file or directory)
            exit(2)

        self.log.info("Successfully parsed config file")

        self.log.debug("Retrieved MIDI port name(s) `%s`" % result)
        #create new class with handler and open from there, just create new instances
        for device in result:
            self._portobjects.append(DeviceHandler(device, device.doc_id))

        self.log.info("Successfully initialized midi port(s)")
        del result

        # close tinydb
        tiny_database.close()

        # setting up a Websocket client
        self.log.debug("Attempting to connect to OBS using websocket protocol")
        self.obs_socket = WebSocketApp("ws://%s:%d" % (ws_server, ws_port))
        self.obs_socket.on_message = self.handle_obs_message
        self.obs_socket.on_error = self.handle_obs_error
        self.obs_socket.on_close = self.handle_obs_close
        self.obs_socket.on_open = self.handle_obs_open

    def handle_midi_input(self, message, deviceID, deviceName):
        self.log.debug("Received %s %s %s %s %s", str(message), "from device",
                       deviceID, "/", deviceName)

        if message.type == "note_on":
            return self.handle_midi_button(deviceID, message.type,
                                           message.note)

        # `program_change` messages can be only used as regular buttons since
        # they have no extra value, unlike faders (`control_change`)
        if message.type == "program_change":
            return self.handle_midi_button(deviceID, message.type,
                                           message.program)

        if message.type == "control_change":
            return self.handle_midi_fader(deviceID, message.control,
                                          message.value)

    def handle_midi_button(self, deviceID, type, note):
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'msg_type == "%s" and msgNoC == %s and deviceID == %s' %
                (type, note, deviceID)))

        if not results:
            self.log.debug("Cound not find action for note %s", note)
            return

        for result in results:
            if self.send_action(result):
                pass

    def handle_midi_fader(self, deviceID, control, value):
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'msg_type == "control_change" and msgNoC == %s and deviceID == %s'
                % (control, deviceID)))

        if not results:
            self.log.debug("Cound not find action for fader %s", control)
            return

        for result in results:
            input_type = result["input_type"]
            action = result["action"]

            if input_type == "button":
                if value == 127 and not self.send_action(result):
                    continue

            if input_type == "fader":
                command = result["cmd"]
                scaled = map_scale(value, 0, 127, result["scale_low"],
                                   result["scale_high"])

                if command == "SetSourceScale":
                    self.obs_socket.send(action.format(scaled))

                # Super dirty hack but @AlexDash says that it works
                # @TODO: find an explanation _why_ it works
                if command == "SetVolume":
                    # Yes, this literally raises a float to a third degree
                    self.obs_socket.send(action % scaled**3)

                if command == "SetGainFilter":
                    self.obs_socket.send(action % scaled)

                if command == "SetSourceRotation" or command == "SetTransitionDuration" or command == "SetSyncOffset" or command == "SetSourcePosition":
                    self.obs_socket.send(action % int(scaled))

    def handle_obs_message(self, message):
        self.log.debug("Received new message from OBS")
        payload = json.loads(message)

        self.log.debug("Successfully parsed new message from OBS: %s" %
                       message)

        if "error" in payload:
            self.log.error("OBS returned error: %s" % payload["error"])
            return

        message_id = payload["message-id"]

        self.log.debug("Looking for action with message id `%s`" % message_id)
        for action in self._action_buffer:
            (buffered_id, template, kind) = action

            if buffered_id != int(payload["message-id"]):
                continue

            del buffered_id
            self.log.info("Action `%s` was requested by OBS" % kind)

            if kind == "ToggleSourceVisibility":
                # Dear lain, I so miss decent ternary operators...
                invisible = "false" if payload["visible"] else "true"
                self.obs_socket.send(template % invisible)
            elif kind == "ReloadBrowserSource":
                source = payload["sourceSettings"]["url"]
                target = source[0:-1] if source[-1] == '#' else source + '#'
                self.obs_socket.send(template % target)
            elif kind == "ToggleSourceFilter":
                invisible = "false" if payload["enabled"] else "true"
                self.obs_socket.send(template % invisible)

            self.log.debug("Removing action with message id %s from buffer" %
                           message_id)
            self._action_buffer.remove(action)
            break

        if message_id == "MIDItoOBSscreenshot":
            if payload["status"] == "ok":
                with open(str(time()) + ".png", "wb") as fh:
                    fh.write(base64.decodebytes(payload["img"][22:].encode()))

    def handle_obs_error(self, ws, error=None):
        # Protection against potential inconsistencies in `inspect.ismethod`
        if error is None and isinstance(ws, BaseException):
            error = ws

        if isinstance(error, (KeyboardInterrupt, SystemExit)):
            self.log.info("Keyboard interrupt received, gracefully exiting...")
            self.close(teardown=True)
        else:
            self.log.error("Websocket error: %" % str(error))

    def handle_obs_close(self, ws):
        self.log.error("OBS has disconnected, timed out or isn't running")
        self.log.error("Please reopen OBS and restart the script")

    def handle_obs_open(self, ws):
        self.log.info("Successfully connected to OBS")

    def send_action(self, action_request):
        action = action_request.get("action")
        if not action:
            # @NOTE: this potentionally should never happen but you never know
            self.log.error("No action supplied in current request")
            return False

        request = action_request.get("request")
        if not request:
            self.log.debug("No request body for action %s, sending action" %
                           action)
            self.obs_socket.send(action)
            # Success, breaking the loop
            return True

        template = TEMPLATES.get(request)
        if not template:
            self.log.error("Missing template for request %s" % request)
            # Keep searching
            return False

        target = action_request.get("target")
        if not target:
            self.log.error("Missing target in %s request for %s action" %
                           (request, action))
            # Keep searching
            return False

        field2 = action_request.get("field2")
        if not field2:
            field2 = False

        self._action_buffer.append([self._action_counter, action, request])
        if field2:
            self.obs_socket.send(template %
                                 (self._action_counter, target, field2))
        else:
            self.obs_socket.send(template % (self._action_counter, target))
        self._action_counter += 1

        # Explicit return is necessary here to avoid extra searching
        return True

    def start(self):
        self.log.info("Connecting to OBS...")
        self.obs_socket.run_forever()

    def close(self, teardown=False):
        self.log.debug("Attempting to close midi port(s)")
        result = self.devdb.all()
        for device in result:
            device.close()

        self.log.info("Midi connection has been closed successfully")

        # If close is requested during keyboard interrupt, let the websocket
        # client tear itself down and make a clean exit
        if not teardown:
            self.log.debug("Attempting to close OBS connection")
            self.obs_socket.close()

            self.log.info("OBS connection has been closed successfully")

        self.log.info("Config file has been successfully released")

    def __end__(self):
        self.log.info("Exiting script...")
        self.close()
Esempio n. 11
0
class voice():
    def __init__(self, server, userDic, adminList, roomId):
        self.userDic = userDic
        self.roomId = roomId
        self.adminList = adminList
        self.server = server
        connectStr = 'ws://%s/socket/websocket?token=%s&nonce=%s'%(self.server, self.userDic['token'], self.userDic['nonce'])
        #print('connectStr: ', connectStr)
        self.ws = WebSocketApp(connectStr, 
            on_message = self.on_message,
            on_error   = self.on_error,
            on_close   = self.on_close)
            
        self.ownerActionDic = {
            0: {'action': 'mute_seat', 'percentage':90, 'parameter': {'targetUserId': self.userDic['id']}},
            1: {'action': 'message', 'percentage': 90, 'parameter': {'content': None}},
            2: {'action': 'phx_leave', 'percentage': 100, 'parameter': {}},
            3: {'action': 'send_sticker', 'percentage':60, 'parameter': {'stickerId': None}},
            4: {'action': 'get_mics_mgm', 'percentage':95, 'parameter': {}},
            5: {'action': 'get_violation', 'percentage':95, 'parameter': {}},
        }
        self.adminActionDic = {
            0: {'action': 'mute_seat', 'percentage':90, 'parameter': {'targetUserId': self.userDic['id']}},
            1: {'action': 'message', 'percentage': 90, 'parameter': {'content': None}},
            2: {'action': 'take_seat', 'percentage': 20, 'parameter': {'seatIndex': self.userDic['seatId']}},
            3: {'action': 'phx_leave', 'percentage': 100, 'parameter': {}},
            4: {'action': 'get_mics_mgm', 'percentage':95, 'parameter': {}},
            5: {'action': 'get_violation', 'percentage':95, 'parameter': {}},
            6: {'action': 'send_sticker', 'percentage':60, 'parameter': {'stickerId': None}},
        }
        self.audienceActionDic = {
            1:{'action': 'message', 'percentage': 80, 'parameter': {'content': None}},
            2:{'action': 'book_seat', 'percentage': 100, 'parameter': {}},
            3:{'action': 'phx_leave', 'percentage': 100, 'parameter': {}},
            4:{'action': 'track', 'percentage': 99, 'parameter': {}},
            5:{'action': 'gift', 'percentage': 98, 'parameter': {'giftId': None, 'targetUserId': None, 'count': None}}
        }

    def actionBody(self, action, parameter):
        payload = {}
        bodyDic = {
            'noPayload': ['phx_join', 'heartbeat', 'phx_leave', 'book_seat', 'leave_seat', 'abort_seat', 'get_mics_mgm', 'get_violation'],
            'take_seat': ['seatIndex'],
            'message': ['content'],
            'mute_seat': ['targetUserId'],
            'unmute_seat': ['targetUserId'],
            'track': ['liveMasterId'],
            'send_sticker': ['stickerId'],
            'gift': ['giftId', 'targetUserId', 'count']
        }
        if action in bodyDic['noPayload']:
            payload = {}
        else:
            for i in bodyDic[action]:
                if all([action == 'gift', i == 'targetUserId']): 
                    payload[i] = parameter[i][0]
                else:
                    payload[i] = parameter[i]
        topicStr = 'phoenix' if action == 'heartbeat' else 'vc_room:' + str(self.roomId)
        body = {
                'ref': str(int(time.time()*1000)),
                'join_ref': str(int(time.time()*1000)),
                'topic': topicStr,
                'event': action,
                'payload': payload
        }
        return body
 
    def on_message(self, message): 
        data = json.loads(message)
        pprint(data)

    def on_error(self, error):
        print('===========Err===============')
        print(self.ws)
        print(error)
        print(self.ws.last_ping_tm,' ', self.ws.last_pong_tm)
        
        # print('===========Reconnect ===============')
        # self.on_connect()

    def on_close(self):
        print('closed connection correctly')
        if self.ws is not None:
            self.ws.close()
            self.ws.on_message = None
            self.ws.on_open= None
            print('del ws object')
            del self.ws

    def on_connect(self):
        self.ws.on_open = self.on_open    
        self.ws.run_forever(ping_interval=10, ping_timeout=5)

    def audience(self):
        msgList = ['我是免費仔,絕對不送禮', '主播姐姐聲音真好聽', '勉強送個禮好了,不然被人嫌', '狗腿一下小馬好帥', '寒流來襲,抱啥可以?', '天冷不出門,下雨不出門,心情不美不出門']
        giftList = ['4b5d7cbe-485c-41dc-a78c-f0b06cf61a25', 'a700b291-362a-42fa-9db4-6d29d4541273', '49853090-e4cd-47da-826e-1131388bd6c4', 'ddc2eadf-e40f-4e33-896f-764674366dd9', '1a51e630-3956-46e0-8bb9-e334f06b5634clear']
        isLeave = False
        actionId = random.randint(1, 5)
        if (random.randint(1, int(time.time())) % 100) >= self.audienceActionDic[actionId]['percentage']:
            if self.audienceActionDic[actionId]['action'] == 'message': self.audienceActionDic[actionId]['parameter']['content'] = msgList[random.randint(0, 5)] 
            if self.audienceActionDic[actionId]['action'] == 'gift':
                self.audienceActionDic[actionId]['parameter']['giftId'] = giftList[random.randint(0, 4)]   
                self.audienceActionDic[actionId]['parameter']['targetUserId'] = self.adminList[random.randint(0, 3)], 
                self.audienceActionDic[actionId]['parameter']['count'] = random.randint(1, 3)          
            self.ws.send(json.dumps(self.actionBody(self.audienceActionDic[actionId]['action'], self.audienceActionDic[actionId]['parameter'])))
            if self.audienceActionDic[actionId]['action'] in ('abort_seat', 'book_seat'): 
                if self.audienceActionDic[actionId]['action'] == 'abort_seat':
                    self.audienceActionDic[actionId]['action'] = 'book_seat'
                    self.audienceActionDic[actionId]['percentage'] = 95
                else:
                    self.audienceActionDic[actionId]['action'] = 'abort_seat'
                    self.audienceActionDic[actionId]['percentage'] = 50
            if self.audienceActionDic[actionId]['action'] == 'phx_leave':
                if random.randint(1, int(time.time())) % 100 >= 60:
                    print('leave room')
                    isLeave = True
                else:
                    self.ws.send(json.dumps(self.actionBody('phx_join', [])))
        return isLeave

    def admin(self):
        isLeave = False
        msgList = ['管理員跟你問好囉', '肚子餓嗎,要不要一起去吃東西', '要吃土了,快送點禮物給我', '喜歡就追蹤我吧', '冷死主播了,送點温暖吧⋯⋯']
        actionId = random.randint(0, 6)
        while all([actionId == 0, self.adminActionDic[2]['action'] == 'take_seat']):
            actionId = random.randint(0, 6)
        if (random.randint(1, int(time.time())) % 100) >= self.adminActionDic[actionId]['percentage']:
            if self.adminActionDic[actionId]['action'] == 'message': self.adminActionDic[actionId]['parameter']['content'] = msgList[random.randint(0, 4)] 
            if self.adminActionDic[actionId]['action'] == 'send_sticker': self.adminActionDic[actionId]['parameter']['stickerId'] = random.randint(11, 55)
            self.ws.send(json.dumps(self.actionBody(self.adminActionDic[actionId]['action'], self.adminActionDic[actionId]['parameter'])))   
            if actionId == 2:         
                if  self.adminActionDic[actionId]['action'] == 'take_seat':               
                    self.adminActionDic[actionId]['action'] = 'leave_seat'
                    self.adminActionDic[actionId]['percentage'] = 100
                else:
                    self.adminActionDic[actionId]['action'] = 'take_seat'
                    self.adminActionDic[actionId]['percentage'] = 20
            if actionId == 0:
                self.adminActionDic[actionId]['action'] = 'unmute_seat' if self.adminActionDic[actionId]['action'] == 'mute_seat' else 'mute_seat'
            if  self.adminActionDic[actionId]['action'] == 'phx_leave':
                if random.randint(1, int(time.time())) % 100 >= 80:
                    print('leave room')
                    isLeave = True
                else:
                    self.ws.send(json.dumps(self.actionBody('phx_join', [])))
        return isLeave

    def owner(self):
        isLeave = False
        msgList = ['房主跟你問好囉', '要注意保暖,携帶雨具喲', '房主吃土中,快送點禮物給我', '喜歡就追蹤我吧', '房內人氣不足⋯⋯冷呀,來點禮物唄']
        actionId = random.randint(0, 5)
        if (random.randint(1, int(time.time())) % 100) >= self.ownerActionDic[actionId]['percentage']:
            if self.ownerActionDic[actionId]['action'] == 'message': self.ownerActionDic[actionId]['parameter']['content'] = msgList[random.randint(0, 4)] 
            if self.ownerActionDic[actionId]['action'] == 'send_sticker': self.ownerActionDic[actionId]['parameter']['stickerId'] = random.randint(11, 55)
            self.ws.send(json.dumps(self.actionBody(self.ownerActionDic[actionId]['action'], self.ownerActionDic[actionId]['parameter'])))
            if actionId == 0:
                self.ownerActionDic[actionId]['action'] = 'unmute_seat' if self.ownerActionDic[actionId]['action'] == 'mute_seat' else 'mute_seat'
            if self.ownerActionDic[actionId]['action'] == 'phx_leave':
                if random.randint(1, int(time.time())) % 100 >= 80: 
                    isLeave = True
                else:
                    self.ws.send(json.dumps(self.actionBody('phx_join', {})))
        return isLeave

    def on_open(self):
        aciotnDic = {'audience': self.audience, 'admin': self.admin, 'owner': self.owner}
        self.ws.send(json.dumps(self.actionBody('phx_join', {})))    
        while 1:
            time.sleep(8)
            self.ws.send(json.dumps(self.actionBody('heartbeat', {})))
            if aciotnDic[self.userDic['idType']](): break
        print('我自己正常結束的')
Esempio n. 12
0
class MidiHandler:
    # Initializes the handler class
    def __init__(self,
                 config_path=args.config,
                 ws_server=args.host,
                 ws_port=args.port):
        # Setting up logging first and foremost
        self.log = get_logger("midi_to_obs")

        # Internal service variables
        self._action_buffer = []
        self._action_counter = 2
        self._portobjects = []

        # Feedback blocking
        self.blockcount = 0
        self.block = False
        #load tinydb configuration database
        self.log.debug("Trying to load config file  from %s" % config_path)
        tiny_database = TinyDB(config_path, indent=4)
        tiny_db = tiny_database.table("keys", cache_size=20)
        tiny_devdb = tiny_database.table("devices", cache_size=20)

        #get all mappings and devices
        self._mappings = tiny_db.all()
        self._devices = tiny_devdb.all()

        #open dbj datebase for mapping and clear
        self.mappingdb = dbj("temp-mappingdb.json")
        self.mappingdb.clear()

        #convert database to dbj in-memory
        for _mapping in self._mappings:
            self.mappingdb.insert(_mapping)

        self.log.debug("Mapping database: `%s`" % str(self.mappingdb.getall()))

        if len(self.mappingdb.getall()) < 1:
            self.log.critical("Could not cache device mappings")
            # ENOENT (No such file or directory)
            exit(2)

        self.log.debug("Successfully imported mapping database")

        result = tiny_devdb.all()
        if not result:
            self.log.critical("Config file %s doesn't exist or is damaged" %
                              config_path)
            # ENOENT (No such file or directory)
            exit(2)

        self.log.info("Successfully parsed config file")

        self.log.debug("Retrieved MIDI port name(s) `%s`" % result)
        #create new class with handler and open from there, just create new instances
        for device in result:
            self._portobjects.append(
                (DeviceHandler(device, device.doc_id), device.doc_id))

        self.log.info("Successfully initialized midi port(s)")
        del result

        # close tinydb
        tiny_database.close()

        # setting up a Websocket client
        self.log.debug("Attempting to connect to OBS using websocket protocol")
        self.obs_socket = WebSocketApp("ws://%s:%d" % (ws_server, ws_port))
        self.obs_socket.on_message = lambda ws, message: self.handle_obs_message(
            ws, message)
        self.obs_socket.on_error = lambda ws, error: self.handle_obs_error(
            ws, error)
        self.obs_socket.on_close = lambda ws: self.handle_obs_close(ws)
        self.obs_socket.on_open = lambda ws: self.handle_obs_open(ws)

    def getPortObject(self, mapping):
        deviceID = mapping.get("out_deviceID", mapping["deviceID"])
        for portobject, _deviceID in self._portobjects:
            if _deviceID == deviceID:
                return portobject

    def handle_midi_input(self, message, deviceID, deviceName):
        self.log.debug("Received %s %s %s %s %s", str(message), "from device",
                       deviceID, "/", deviceName)

        if message.type == "note_on":
            return self.handle_midi_button(deviceID, message.channel,
                                           message.type, message.note)

        # `program_change` messages can be only used as regular buttons since
        # they have no extra value, unlike faders (`control_change`)
        if message.type == "program_change":
            return self.handle_midi_button(deviceID, message.channel,
                                           message.type, message.program)

        if message.type == "control_change":
            return self.handle_midi_fader(deviceID, message.channel,
                                          message.control, message.value)

    def handle_midi_button(self, deviceID, channel, type, note):
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'msg_channel == %s and msg_type == "%s" and msgNoC == %s and deviceID == %s'
                % (channel, type, note, deviceID)))

        if not results:
            self.log.debug("Cound not find action for note %s", note)
            return

        for result in results:
            if self.send_action(result):
                pass

    def handle_midi_fader(self, deviceID, channel, control, value):
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'msg_channel == %s and msg_type == "control_change" and msgNoC == %s and deviceID == %s'
                % (channel, control, deviceID)))

        if not results:
            self.log.debug("Cound not find action for fader %s", control)
            return
        if self.block == True:
            if self.blockcount <= 4:
                self.log.debug(
                    "Blocked incoming message due to sending message")
                self.block = False
                self.blockcount += 1
        else:
            self.blockcount = 0
            for result in results:
                input_type = result["input_type"]
                action = result["action"]

                if input_type == "button":
                    if value == 127 and not self.send_action(result):
                        continue

                if input_type == "fader":
                    command = result["cmd"]
                    scaled = map_scale(value, 0, 127, result["scale_low"],
                                       result["scale_high"])

                    if command == "SetSourceScale":
                        self.obs_socket.send(action.format(scaled))

                    # Super dirty hack but @AlexDash says that it works
                    # @TODO: find an explanation _why_ it works
                    if command == "SetVolume":
                        # Yes, this literally raises a float to a third degree
                        self.obs_socket.send(action % scaled**3)

                    if command in [
                            "SetGainFilter", "SetOpacity",
                            "SetColorCorrectionHueShift",
                            "Filter/Chroma Key - Contrast",
                            "Filter/Chroma Key - Brightness",
                            "Filter/Chroma Key - Gamma",
                            "Filter/Luma Key - Luma Max",
                            "Filter/Luma Key - Luma Max Smooth",
                            "Filter/Luma Key - Luma Min",
                            "Filter/Luma Key - Luma Min Smooth",
                            "Filter/Color Correction - Saturation",
                            "Filter/Color Correction - Contrast",
                            "Filter/Color Correction - Brightness",
                            "Filter/Color Correction - Gamma",
                            "Filter/Color Correction - Hue Shift",
                            "Filter/Color Key - Brightness",
                            "Filter/Color Key - Contrast",
                            "Filter/Color Key - Gamma",
                            "Filter/Sharpen - Sharpness"
                    ]:
                        self.obs_socket.send(action % scaled)

                    if command in [
                            "SetSourceRotation", "SetTransitionDuration",
                            "SetSyncOffset", "SetSourcePosition",
                            "Filter/Chroma Key - Opacity",
                            "Filter/Chroma Key - Spill Reduction",
                            "Filter/Chroma Key - Similarity",
                            "Filter/Color Key - Similarity",
                            "Filter/Color Key - Smoothness",
                            "Filter/Scroll - Horizontal Speed",
                            "Filter/Scroll - Vertical Speed"
                    ]:
                        self.obs_socket.send(action % int(scaled))

    def handle_obs_message(self, ws, message):
        self.log.debug("Received new message from OBS")
        payload = json.loads(message)

        self.log.debug("Successfully parsed new message from OBS: %s" %
                       message)

        if "error" in payload:
            self.log.error("OBS returned error: %s" % payload["error"])
            return

        if "message-id" in payload:
            message_id = payload["message-id"]

            self.log.debug("Looking for action with message id `%s`" %
                           message_id)
            for action in self._action_buffer:
                (buffered_id, template, kind) = action

                if buffered_id != int(payload["message-id"]):
                    continue

                del buffered_id
                self.log.info("Action `%s` was requested by OBS" % kind)

                if kind == "ToggleSourceVisibility":
                    # Dear lain, I so miss decent ternary operators...
                    invisible = "false" if payload["visible"] else "true"
                    self.obs_socket.send(template % invisible)
                elif kind == "ReloadBrowserSource":
                    source = payload["sourceSettings"]["url"]
                    target = source[0:-1] if source[-1] == '#' else source + '#'
                    self.obs_socket.send(template % target)
                elif kind == "ToggleSourceFilter":
                    invisible = "false" if payload["enabled"] else "true"
                    self.obs_socket.send(template % invisible)
                elif kind in ["SetCurrentScene", "SetPreviewScene"]:
                    self.sceneChanged(kind, payload["name"])

                self.log.debug(
                    "Removing action with message id %s from buffer" %
                    message_id)
                self._action_buffer.remove(action)
                break

            if message_id == "MIDItoOBSscreenshot":
                if payload["status"] == "ok":
                    with open(str(time()) + ".png", "wb") as fh:
                        fh.write(
                            base64.decodebytes(payload["img"][22:].encode()))

        elif "update-type" in payload:
            update_type = payload["update-type"]
            self.log.debug(update_type)
            request_types = {
                "PreviewSceneChanged": "SetPreviewScene",
                "SwitchScenes": "SetCurrentScene"
            }
            if update_type in request_types:
                scene_name = payload["scene-name"]
                self.sceneChanged(request_types[update_type], scene_name)
            elif update_type == "SourceVolumeChanged":
                self.volChanged(payload["sourceName"], payload["volume"])

    def volChanged(self, source_name, volume):
        self.log.info("Volume " + source_name + " changed to val: " +
                      str(volume))
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'input_type == "fader" and bidirectional == 1'))
        if not results:
            self.log.info("no fader results")
            return
        for result in results:

            j = result["action"] % "0"
            k = json.loads(j)["source"]
            self.log.info(k)
            if k == source_name:
                val = int(
                    map_scale(volume, result["scale_low"],
                              result["scale_high"], 0, 127))
                self.log.info(val)

                msgNoC = result.get("out_msgNoC", result["msgNoC"])
                self.log.info(msgNoC)
                portobject = self.getPortObject(result)
                if portobject and portobject._port_out:
                    self.block = True
                    portobject._port_out.send(
                        mido.Message('control_change',
                                     channel=0,
                                     control=int(result["msgNoC"]),
                                     value=val))

    def sceneChanged(self, event_type, scene_name):
        self.log.debug("Scene changed, event: %s, name: %s" %
                       (event_type, scene_name))
        # only buttons can change the scene, so we can limit our search to those
        results = self.mappingdb.getmany(
            self.mappingdb.find(
                'input_type == "button" and bidirectional == 1'))
        if not results:
            return
        for result in results:
            j = json.loads(result["action"])
            if j["request-type"] != event_type:
                continue
            msgNoC = result.get("out_msgNoC", result["msgNoC"])
            channel = result.get("out_channel", 0)
            portobject = self.getPortObject(result)
            if portobject and portobject._port_out:
                if result["msg_type"] == "control_change":
                    value = 127 if j["scene-name"] == scene_name else 0
                    portobject._port_out.send(
                        mido.Message(type="control_change",
                                     channel=channel,
                                     control=msgNoC,
                                     value=value))
                elif result["msg_type"] == "note_on":
                    velocity = 1 if j["scene-name"] == scene_name else 0
                    portobject._port_out.send(
                        mido.Message(type="note_on",
                                     channel=channel,
                                     note=msgNoC,
                                     velocity=velocity))

    def handle_obs_error(self, ws, error=None):
        # Protection against potential inconsistencies in `inspect.ismethod`
        if error is None and isinstance(ws, BaseException):
            error = ws

        if isinstance(error, (KeyboardInterrupt, SystemExit)):
            self.log.info("Keyboard interrupt received, gracefully exiting...")
        else:
            self.log.error("Websocket error: %" % str(error))

    def handle_obs_close(self, ws):
        self.log.error("OBS has disconnected, timed out or isn't running")
        self.log.error("Please reopen OBS and restart the script")
        self.close(teardown=True)

    def handle_obs_open(self, ws):
        self.log.info("Successfully connected to OBS")

        # initialize bidirectional controls
        self.send_action({
            "action": 'GetCurrentScene',
            "request": "SetCurrentScene",
            "target": ":-)"
        })
        self.send_action({
            "action": 'GetPreviewScene',
            "request": "SetPreviewScene",
            "target": ":-)"
        })

    def send_action(self, action_request):
        action = action_request.get("action")
        if not action:
            # @NOTE: this potentionally should never happen but you never know
            self.log.error("No action supplied in current request")
            return False

        request = action_request.get("request")
        if not request:
            self.log.debug("No request body for action %s, sending action" %
                           action)
            self.obs_socket.send(action)
            # Success, breaking the loop
            return True

        template = TEMPLATES.get(request)
        if not template:
            self.log.error("Missing template for request %s" % request)
            # Keep searching
            return False

        target = action_request.get("target")
        if not target:
            self.log.error("Missing target in %s request for %s action" %
                           (request, action))
            # Keep searching
            return False

        field2 = action_request.get("field2")
        if not field2:
            field2 = False

        self._action_buffer.append([self._action_counter, action, request])
        if field2:
            self.obs_socket.send(template %
                                 (self._action_counter, target, field2))
        else:
            self.obs_socket.send(template % (self._action_counter, target))
        self._action_counter += 1

        # Explicit return is necessary here to avoid extra searching
        return True

    def start(self):
        self.log.info("Connecting to OBS...")
        self.obs_socket.run_forever()

    def close(self, teardown=False):
        # set bidirectional controls to their 0 state (i.e., turn off LEDs)
        self.log.debug("Attempting to turn off bidirectional controls")
        result = self.mappingdb.getmany(
            self.mappingdb.find('bidirectional == 1'))
        if result:
            for row in result:
                msgNoC = row.get("out_msgNoC", row["msgNoC"])
                channel = row.get("out_channel", 0)
                portobject = self.getPortObject(row)
                if portobject and portobject._port_out:
                    if row["msg_type"] == "control_change":
                        portobject._port_out.send(
                            mido.Message(type="control_change",
                                         channel=channel,
                                         control=msgNoC,
                                         value=0))
                    elif row["msg_type"] == "note_on":
                        portobject._port_out.send(
                            mido.Message(type="note_on",
                                         channel=channel,
                                         note=msgNoC,
                                         velocity=0))

        self.log.debug("Attempting to close midi port(s)")
        for portobject, _ in self._portobjects:
            portobject.close()

        self.log.info("Midi connection has been closed successfully")

        # If close is requested during keyboard interrupt, let the websocket
        # client tear itself down and make a clean exit
        if not teardown:
            self.log.debug("Attempting to close OBS connection")
            self.obs_socket.close()

            self.log.info("OBS connection has been closed successfully")

        self.log.info("Config file has been successfully released")

    def __end__(self):
        self.log.info("Exiting script...")
        self.close()
Esempio n. 13
0
class WebSockets(object):
    def __init__(self, url, topic):
        self.url = url
        self.topic = topic
        self.connection = None

    def connect(self):
        logger.debug('connect()')
        self.connection = WebSocketApp(
            self.url,
            on_open=self.on_open,
            on_close=self.on_close,
            on_message=self.on_message,
            on_error=self.on_error,
        )
        sslopt = {
            'cert_reqs': CERT_NONE,
        }
        self.connection.run_forever(sslopt=sslopt)

    def disconnect(self):
        logger.debug('disconnect()')
        if self.connection:
            self.connection.close()

    def send(self, payload, *args, **kwargs):
        logger.debug(payload)
        self.connection.send(payload)

    def on_open(self, _):
        logger.debug('on_open()')
        pass

    def on_close(self, _):
        logger.debug('on_close()')
        pass

    def on_message(self, _, payload):
        logger.debug('on_message()')
        prefix, message = self.parse(payload)
        if prefix == '0':
            return
        if prefix == '3':
            return
        if prefix == '40':
            message = [
                'subscribe',
                {
                    'Topic': self.topic,
                    'ConditionsUpdates': 'true',
                    'LiveUpdates': 'true',
                    'OddsUpdates': 'false',
                    'VideoUpdates': 'false',
                },
            ]
            message = dumps(message)
            message = '{prefix:d}{message:s}'.format(prefix=42,
                                                     message=message)
            self.send(message)
            return
        if prefix == '42':
            message = loads(message)
            if 'ActiveMQMessage' in message[1]:
                message[1]['ActiveMQMessage'] = loads(
                    message[1]['ActiveMQMessage'])
                mlu = message[1]['ActiveMQMessage']['MLU']
                t = mlu.get('T', [])
                eid = mlu.get('EID', '?')
                en = mlu.get('EN', '?')
                logger.info((mlu['CPT'], mlu['CR'], mlu['PSID'], mlu['TSID'],
                             mlu['SCH'], mlu['SCA'], len(t), eid, en))
            message = '2'
            self.send(message)
            return

    def parse(self, payload):
        prefix = []
        message = payload
        while True:
            if not message:
                break
            character = message[0]
            if not character.isdigit():
                break
            prefix.append(character)
            message = message[1:]
        prefix = ''.join(prefix)
        return prefix, message

    def on_error(self, _, error):
        logger.debug('on_error()')
        logger.debug(error)
        pass
Esempio n. 14
0
class ResClient(object):

    # Creates a ResClient instance
    # @param {string} hostUrl Websocket host path. May be relative to current path.
    # @param {object} [opt] Optional parameters.
    # @param {function} [opt.onConnect] On connect callback called prior resolving the connect promise and subscribing to stale resources. May return a promise.
    # @param {string} [opt.namespace] Event bus namespace. Defaults to 'resclient'.
    # @param {module:modapp~EventBus} [opt.eventBus] Event bus.
    def __init__(self, hostUrl, opt=None):
        self.hostUrl = self._resolvePath(hostUrl)
        self.loop = asyncio.new_event_loop()
        t = threading.Thread(target=run_loop, args=(self.loop, ))
        t.start()
        print('thread started')

        self.event_queues = dict()
        self.subscribe_queues = dict()
        self.tasks = dict()
        self.ws = WebSocketApp(self.hostUrl,
                               on_message=self._handleOnmessage,
                               on_error=self._handleOnerror,
                               on_close=self._handleOnclose,
                               on_open=self._handleOnopen)
        self.tryConnect = False
        self.connected = False
        self.requests = {}
        self.reqId = 1  # Incremental request id
        self.cache = {}
        self.stale = None
        self.logger = logging.getLogger()
        self.logger.addHandler(logging.StreamHandler())
        self.logger.setLevel(logging.DEBUG)

        def prepare_model_data(data):
            #create a copy of data called _data
            _data = {k: v for k, v in data.items()}
            for k, v in _data.items():
                if isinstance(v, dict) and 'rid' in v:
                    ci = self.cache[v['rid']]
                    ci.addIndirect()
                    _data[k] = ci.item
            return _data

        def prepare_list_data(data):
            def _f(v):
                # Is the value a reference, get the actual item from cache
                if isinstance(v, dict) and 'rid' in v:
                    ci = self.cache[v['rid']]
                    ci.addIndirect()
                    return ci.item
                return v

            return list(map(_f, data))

        # Types
        self.types = {
            'model': {
                'id':
                typeModel,
                'list':
                TypeList(defaultModelFactory),
                'prepareData':
                prepare_model_data,
                'getFactory':
                lambda rid: self.types['model']['list'].getFactory(rid),
                'syncronize':
                self._syncModel
            },
            'collection': {
                'id':
                typeCollection,
                'list':
                TypeList(defaultCollectionFactory),
                'prepareData':
                prepare_list_data,
                'getFactory':
                lambda rid: self.types['collection']['list'].getFactory(rid),
                'syncronize':
                self._syncCollection
            },
            'error': {
                'id': typeError,
                'prepareData': lambda dta: dta,
                'getFactory': lambda rid: errorFactory,
                'syncronize': lambda: None
            }
        }

    # Disconnects any current connection and stops attempts
    # of reconnecting.
    def disconnect(self):
        self.tryConnect = False

        if self.ws:
            self.ws.close()
            #self._connectReject({ 'code': 'system.disconnect', 'message': "Disconnect called" })

    def run(self):
        self.ws.run_forever()

    # Gets the host URL to the RES API
    # @returns {string} Host URL
    def getHostUrl(self):
        return self.hostUrl

    def add_event(self, event, cb):
        if event not in self.event_queues:
            self.event_queues[event] = asyncio.queues.Queue(maxsize=1000,
                                                            loop=self.loop)

        async def handle(q):
            while True:
                result = await q.get()
                cb(result)

        task = asyncio.run_coroutine_threadsafe(
            handle(self.event_queues[event]), self.loop)
        self.tasks[event] = task

    # Attach an  event handler function for one or more instance events.
    # @param {?string} events One or more space-separated events. Null means any event.
    # @param {eventCallback} handler A function to execute when the event is emitted.
    def on(self, events, handler):
        events = events.split(' ')
        for event in events:
            print('adding handler for ', event)
            self.add_event(event, handler)

    # Remove an instance event handler.
    # @param {?string} events One or more space-separated events. Null means any event.
    # @param {eventCallback} [handler] An optional handler function. The handler will only be remove if it is the same handler.
    def off(self, events, handler):
        for event in events.split(' '):
            try:
                self.tasks[event].cancel()
            except:
                self.logger.exception(
                    'Error turning off resource handler for {}'.format(event))

    # Sets the onConnect callback.
    # @param {?function} onConnect On connect callback called prior resolving the connect promise and subscribing to stale resources. May return a promise.
    def setOnConnect(self, onConnect):
        self.onConnect = onConnect
        return self

    # Register a model type.
    # The pattern may use the following wild cards:
    # # The asterisk (#) matches any part at any level of the resource name.
    # # The greater than symbol (>) matches one or more parts at the end of a resource name, and must be the last part.
    # @param {string} pattern Pattern of the model type.
    # @param {resourceFactoryCallback} factory Model factory callback
    def registerModelType(self, pattern, factory):
        self.types['model']['list'].addFactory(pattern, factory)

    # Unregister a previously registered model type pattern.
    # @param {string} pattern Pattern of the model type.
    # @returns {resourceFactoryCallback} Unregistered model factory callback
    def unregisterModelType(self, pattern):
        return self.types['model']['list'].removeFactory(pattern)

    # Register a collection type.
    # The pattern may use the following wild cards:
    # # The asterisk (#) matches any part at any level of the resource name.
    # # The greater than symbol (>) matches one or more parts at the end of a resource name, and must be the last part.
    # @param {string} pattern Pattern of the collection type.
    # @param {ResClient~resourceFactoryCallback} factory Collection factory callback
    def registerCollectionType(self, pattern, factory):
        self.types['collection']['list'].addFactory(pattern, factory)

    # Unregister a previously registered collection type pattern.
    # @param {string} pattern Pattern of the collection type.
    # @returns {resourceFactoryCallback} Unregistered collection factory callback
    def unregisterCollectionType(self, pattern):
        return self.types['model']['list'].removeFactory(pattern)

    # Get a resource from the API
    # @param {string} rid Resource ID
    # @param {function} [collectionFactory] Collection factory function.
    # @return {Promise.<(ResModel|ResCollection)>} Promise of the resource.
    async def get(self, rid):
        # Check for resource in cache
        if rid in self.cache:
            ci = self.cache[rid]
            return ci.item

        ci = CacheItem(rid, self._unsubscribe, self.loop)
        self.cache[rid] = ci
        print('await subscribe')
        await self._subscribe(ci, True)
        print('finished subscribe')
        return ci.item

    # Calls a method on a resource.
    # @param {string} rid Resource ID.
    # @param {string} method Method name
    # @param {#} params Method parameters
    # @returns {Promise.<object>} Promise of the call result.
    async def call(self, rid, method, params):
        if method == None:
            method = ''
        await self._send('call', rid, method, params)

    # Invokes a authentication method on a resource.
    # @param {string} rid Resource ID.
    # @param {string} method Method name
    # @param {#} params Method parameters
    # @returns {Promise.<object>} Promise of the authentication result.
    async def authenticate(self, rid, method, params):
        if method == None:
            method = ''
        await self._send('auth', rid, method, params)

    # Creates a new resource by calling the 'new' method.
    # @param {#} rid Resource ID
    # @param {#} params Method parameters
    # @return {Promise.<(ResModel|ResCollection)>} Promise of the resource.
    async def create(self, rid, params):
        response = await self._send('new', rid, None, params)
        self._cacheResources(response)
        ci = self.cache[response.rid]
        ci.setSubscribed(True)
        return ci.item

    # Calls the set method to update model properties.
    # @param {string} modelId Model resource ID.
    # @param {object} props Properties. Set value to undefined to delete a property.
    # @returns {Promise.<object>} Promise of the call being completed.
    async def setModel(self, modelId, props):
        _props = {k: v for k, v in props.items()}
        # Replace undefined with actionDelete object
        for k, v in _props.items():
            if v == 'undefined':
                _props[k] = 'actionDelete'
        return await self._send('call', modelId, 'set', props)

    def resourceOn(self, rid, events, handler):
        cacheItem = self.cache[rid]
        if not cacheItem:
            raise Exception("Resource not found in cache")
        cacheItem.addDirect()
        for event in events.split(' '):
            self.add_handler(event, cacheItem, handler)

    def add_handler(self, event, cacheItem, handler):
        if event not in cacheItem.queues:
            cacheItem.queues[event] = asyncio.Queue(1000, loop=self.loop)

        async def handle(q):
            while True:
                result = await q.get()
                #asyncio.run_coroutine_threadsafe(handler(result), loop=self.loop)
                handler(result)

        task = asyncio.run_coroutine_threadsafe(handle(
            cacheItem.queues[event]),
                                                loop=self.loop)
        cacheItem.tasks[event] = task

    def remove_handler(self, event, cacheItem):
        if event not in cacheItem.tasks:
            raise Exception('Resourse has no handler')
        try:
            cacheItem.tasks[event].cancel()
        except:
            pass

    def resourceOff(self, rid, events):
        cacheItem = self.cache[rid]
        if not cacheItem:
            raise Exception("Resource not found in cache")

        cacheItem.removeDirect()
        for event in events.split(' '):
            self.remove_handler(event, cacheItem)

    # Sends a JsonRpc call to the API
    # @param {string} action Action name
    # @param {string} rid Resource ID
    # @param {?string} method Optional method name
    # @param {?object} params Optional parameters
    # @returns {Promise.<object>} Promise to the response
    # @private
    async def _send(self, action, rid, method, params):
        if not rid:
            raise Exception("Invalid resource ID")

        if method == "":
            raise Exception("Invalid method")

        if method is None:
            method = ''

        method = action + '.' + rid + method
        if self.connected:
            print('awaiting _sendNow')
            return await self._sendNow(method, params)

        else:
            raise Exception('New ResError {} {} {}'.format(
                rid, method, params))

    async def _sendNow(self, method, params):
        #return new Promise((resolve, reject) => {
        # Prepare request object
        self.reqId += 1
        req = {'id': self.reqId, 'method': method, 'params': params}
        self.requests[req['id']] = {
            'method': method,
            'params': req['params'],
            'resolve': asyncio.Future(loop=self.loop)
        }
        keys = [k for k in req.keys()]
        for k in keys:
            if req[k] is None:
                del req[k]

        self.ws.send(json.dumps(req))
        self.logger.debug('waitng to resolve req id {}'.format(req['id']))
        ret = await self.requests[req['id']]['resolve']
        del self.requests[req['id']]
        return ret

    # Recieves a incoming json encoded data string and executes the appropriate functions/callbacks.
    # @param {string} json Json encoded data
    # @private
    def _receive(self, data):
        data = json.loads(data)
        print('datat received from server')

        if 'id' in data:
            # Find the stored request
            req = self.requests[data['id']]
            print('resolve id = ', data['id'])
            if not req:
                raise Exception("Server response without matching request")

            if 'error' in data:
                self._handleErrorResponse(req, data)
            else:
                self._handleSuccessResponse(req, data)

        elif 'event' in data:
            self._handleEvent(data)
        else:
            raise Exception("Invalid message from server: {}".format(data))

    def _handleErrorResponse(self, req, data):
        m = req['method']
        # Extract the rid if possible
        rid = ""
        split_message = m.split('.')
        if len(split_message) > 0:
            rid = m[1]
            a = m[0]
            if a == 'call' or a == 'auth':
                rid = '.'.join(m[1:-1])

        err = ResError(rid.strip(), m, req['params'])
        err._init(data['error'])
        self._emit('error', err)

        # Execute error callback bound to calling object
        req['resolve'].set_exception(err)

    def _handleSuccessResponse(self, req, data):
        # Execute success callback bound to calling object
        if 'result' in data:
            self.loop.call_soon_threadsafe(req['resolve'].set_result,
                                           data['result'])
        else:
            self.loop.call_soon_threadsafe(req['resolve'].set_result, data)
        print('resolving future for ', req['resolve'])

    def _handleEvent(self, data):
        # Event
        print('handle event')
        idx = data['event'].rfind('.')
        if idx < 0 or idx == len(data['event']) - 1:
            raise Exception("Malformed event name: " + data.event)

        rid = data['event'][0:idx]

        cacheItem = self.cache[rid]
        if not cacheItem:
            raise Exception("Resource not found in cache")

        event = data['event'][idx + 1:]
        handled = False

        if event == 'change':
            print('handle change event')
            handled = self._handleChangeEvent(cacheItem, event, data['data'])
        elif event == 'add':
            handled = self._handleAddEvent(cacheItem, event, data['data'])
        elif event == 'remove':
            handled = self._handleRemoveEvent(cacheItem, event, data['data'])
        elif event == 'unsubscribe':
            handled = self._handleUnsubscribeEvent(cacheItem, event)

        if not handled:
            if event in cacheItem.queues:
                asyncio.run_coroutine_threadsafe(cacheItem.queues[event].put(
                    data['data']),
                                                 loop=self.loop)

    def _handleChangeEvent(self, cacheItem, event, data):
        if cacheItem.type != typeModel:
            return False
        self._cacheResources(data)

        # Set deleted properties to undefined
        item = cacheItem.item
        rm = {}
        vals = data['values']
        for k, v in vals.items():
            if v is not None and isinstance(v, dict):
                if v.get('action') == 'delete':
                    vals[k] = None
                elif v.get('rid') is not None:
                    ci = self.cache[v.get('rid')]
                    vals[k] = ci.item
                    if v['rid'] in rm:
                        rm[v['rid']] -= 1
                    else:
                        rm[v['rid']] = -1
                else:
                    raise Exception("Unsupported model change value: ", v)

            ov = item.data.get(k)
            if self._isResource(ov):
                rid = ov.getResourceId()
                if 'rid' in rm:
                    rm[rid] += 1
                else:
                    rm[rid] = 1

    # Remove indirect reference to resources no longer referenced in the model
        for rid, resource in rm.items():
            ci = self.cache[rid]
            ci.removeIndirect(resource)
            if resource > 0:
                self._tryDelete(ci)

    # Update the model with new values
        changed = cacheItem.item._update(vals)
        if changed:
            if event in cacheItem.queues:
                print('data has changed putting  to  queue')
                asyncio.run_coroutine_threadsafe(cacheItem.queues[event].put(
                    cacheItem.item),
                                                 loop=self.loop)
        return True

    def _handleAddEvent(self, cacheItem, event, data):
        if cacheItem.type != typeCollection:
            return False

        idx = data['idx']

        # Get resource if value is a resource reference
        if data.get('value') is not None and data.get('value').get(
                'rid') is not None:
            self._cacheResources(data)
            ci = self.cache[data['value']['rid']]
            ci.addIndirect()
            data['value'] = ci.item

        cacheItem.item._add(data.get('value'), idx)
        if event in cacheItem.queues:
            asyncio.run_coroutine_threadsafe(cacheItem.queues[event].put({
                'item':
                data.get('value'),
                'idx':
                idx
            }),
                                             loop=self.loop)
        return True

    def _handleRemoveEvent(self, cacheItem, event, data):
        if cacheItem.type != typeCollection:
            return False
        idx = data['idx']
        item = cacheItem.item._remove(idx)
        if event in cacheItem.queues:
            asyncio.run_coroutine_threadsafe(cacheItem.queues[event].put({
                'item':
                item,
                'idx':
                idx
            }),
                                             loop=self.loop)

        if self._isResource(item):
            refItem = self.cache[item.getResourceId()]
            if not refItem:
                raise Exception("Removed model is not in cache")

            refItem.removeIndirect()
            self._tryDelete(refItem)
        return True

    def _handleUnsubscribeEvent(self, cacheItem, event):
        cacheItem.setSubscribed(False)
        self._tryDelete(cacheItem)
        if event in cacheItem.queues:
            asyncio.run_coroutine_threadsafe(cacheItem.queues[event].put(
                {'item': cacheItem.item}),
                                             loop=self.loop)
        return True

    async def _setStale(self, rid):
        self._addStale(rid)
        if self.connected:
            await asyncio.sleep(subscribeStaleDelay / 1000)
            self._subscribeToStale(rid)

    def _addStale(self, rid):
        if not self.stale:
            self.stale = {}
        self.stale[rid] = True

    def _removeStale(self, rid):
        if self.stale:
            del self.stale[rid]
            for k in self.stale:
                return
            self.stale = None

    async def _subscribe(self, ci, throwError):
        rid = ci.rid
        ci.setSubscribed(True)
        self._removeStale(rid)
        try:
            response = await self._send('subscribe', rid, None, None)
            self._cacheResources(response)
        except:
            self.logger.exception('error subscribing to resource ' + str(rid))
            self._handleFailedSubscribe(ci, None)
            if throwError:
                raise Exception

    def _subscribeToStale(self, rid):
        if not self.connected or not self.stale or not self.stale[rid]:
            return
        self._subscribe(self.cache[rid])

    def _subscribeToAllStale(self):
        if (not self.stale):
            return

        for rid in self.stale:
            self._subscribeToStale(rid)

    # Handles the websocket onopen event
    # @param {object} e Open event object
    # @private
    def _handleOnopen(self, ws):
        self.logger.debug('Connection to {} opened'.format(self.hostUrl))
        self.connected = True
        try:
            asyncio.run_coroutine_threadsafe(self.onConnect(), self.loop)
            self._subscribeToAllStale()
        except:
            if self.ws:
                self.ws.close()

    # Handles the websocket onerror event
    # @param {object} e Error event object
    # @private
    def _handleOnerror(self, ws, error):
        self.logger.debug(
            json.dumps({
                'code': 'system.connectionError',
                'message': "Connection error",
                'data': str(error)
            }))

    # Handles the websocket onmessage event
    # @param {object} e Message event object
    # @private
    def _handleOnmessage(self, ws, msg):
        self.logger.debug('new message - {}'.format(msg))
        #msg = json.loads(msg)
        self._receive(msg)

    # Handles the websocket onclose event
    # @param {object} e Close event object
    # @private
    def _handleOnclose(self, ws):
        self.logger.debug('websocket closed')
        self.ws = None
        if self.connected:
            self.connected = False

            # Set any subscribed item in cache to stale
            for rid, ci in self.cache.items():
                if ci.subscribed:
                    ci.setSubscribed(False)
                    self._addStale(rid)
                    self._tryDelete(ci)

            self._emit('close', ws)

        hasStale = False
        for _ in self.cache:
            hasStale = True
            break

        self.tryConnect = hasStale and self.tryConnect

        if self.tryConnect:
            self._reconnect()

    def _emit(self, event, data):
        if event not in self.event_queues:
            self.event_queues[event] = asyncio.Queue(1000, loop=self.loop)
        asyncio.run_coroutine_threadsafe(self.event_queues[event].put(data),
                                         loop=self.loop)

    # Tries to delete the cached item.
    # It will delete if there are no direct listeners, indirect references, or any subscription.
    # @param {object} ci Cache item to delete
    # @private
    def _tryDelete(self, ci):
        try:
            refs = self._getRefState(ci)

            for rid, r in refs.items():
                if r['st'] == stateStale:
                    self._setStale(rid)
                    self.logger.debug('{} set to stale'.format(rid))
                elif r['st'] == stateDelete:
                    self._deleteRef(r['ci'])
                    self.logger.debug('references to {} deleted'.format(rid))
        except:
            self.logger.exception(
                'exception in trying to delete cached item {}'.format(ci.rid))

    # Gets the reference state for a cacheItem and all its references
    # if the cacheItem was to be removed.
    # @param {CacheItem} ci Cache item
    # @return {Object.<string, RefState>} A key value object with key being the rid, and value being a RefState array.
    # @private
    def _getRefState(self, ci):
        refs = {}
        # Quick exit
        if (ci.subscribed):
            return refs
        refs[ci.rid] = {'ci': ci, 'rc': ci.indirect, 'st': stateNone}
        self._traverse(ci, refs, self._seekRefs, 0, True)
        self._traverse(ci, refs, self._markDelete, stateDelete)
        return refs

    # Seeks for resources that no longer has any reference and may
    # be deleted.
    # Callback used with _traverse.
    # @param {#} refs References
    # @param {#} ci Cache item
    # @param {#} state State as returned from parent's traverse callback
    # @returns {#} State to pass to children. False means no traversing to children.
    # @private
    def _seekRefs(self, refs, ci, state):
        # Quick exit if it is already subscribed
        if (ci.subscribed):
            return False

        rid = ci.rid
        r = refs.get(rid)
        if not r:
            refs[rid] = {'ci': ci, 'rc': ci.indirect - 1, 'st': stateNone}
            return True

        r['rc'] -= 1
        return False

    # Marks reference as stateDelete, stateKeep, or stateStale, depending on
    # the values returned from a _seekRefs traverse.
    # @param {#} refs References
    # @param {#} ci Cache item
    # @param {#} state State as returned from parent's traverse callback
    # @return {#} State to pass to children. False means no traversing to children.
    # @private
    def _markDelete(self, refs, ci, state):
        # Quick exit if it is already subscribed
        if (ci.subscribed):
            return False

        rid = ci.rid
        r = refs[rid]

        if (r['st'] == stateKeep):
            return False

        if (state == stateDelete):

            if (r['rc'] > 0):
                r['st'] = stateKeep
                return rid

            if (r['st'] != stateNone):
                return False

            if (r['ci'].direct):
                r['st'] = stateStale
                return rid

            r['st'] = stateDelete
            return stateDelete

    # A stale item can never cover itself
        if (rid == state):
            return False

        r['st'] = stateKeep
        return rid if r['rc'] > 0 else state

    def _deleteRef(self, ci):
        item = ci.item
        if ci.type == typeCollection:
            for v in item:
                ri = self._getRefItem(v)
                if (ri):
                    ri.removeIndirect()
        elif ci.type == typeModel:
            for k in item.data:
                ri = self._getRefItem(item.data[k])
                if (ri):
                    ri.removeIndirect()
        del self.cache[ci.rid]
        self._removeStale(ci.rid)

    def _isResource(self, v):
        return v != None and hasattr(v, 'getResourceId') and callable(
            v.getResourceId)

    def _getRefItem(self, v):
        if not self._isResource(v):
            return None
        rid = v.getResourceId()
        refItem = self.cache[rid]
        # refItem not in cache means
        # item has been deleted as part of
        # a refState object.
        if not refItem:
            return None

        return refItem

    def _cacheResources(self, resources):
        if not resources:
            return
        sync = dict()
        for t in resourceTypes:
            if t + 's' in resources:
                sync[t] = self._createItems(resources[t + 's'], self.types[t])
                self._initItems(resources[t + 's'], self.types[t])
                self._syncItems(sync[t], self.types[t])

    def _createItems(self, refs, _type):
        if not refs:
            return
        sync = {}
        for rid in refs:
            ci = self.cache.get(rid)
            if ci is None:
                self.cache[rid] = CacheItem(rid, self._unsubscribe, self.loop)
                ci = self.cache[rid]
            else:
                # Remove item as stale if needed
                self._removeStale(rid)
            # If an item is already set,
            # it has gone stale and needs to be syncronized.
            if ci.item:
                if ci.type != _type.id:
                    self.logger.error("Resource type inconsistency")
                else:
                    sync[rid] = refs[rid]
                del refs[rid]
            else:
                f = _type['getFactory'](rid)
                ci.setItem(f(self, rid), _type['id'])
        return sync

    def _initItems(self, refs, _type):
        if refs == None:
            return

        for rid in refs:
            cacheItem = self.cache[rid]
            cacheItem.item._init(_type['prepareData'](refs[rid]))

    def _syncItems(self, refs, _type):
        if refs == None:
            return

        for rid in refs:
            ci = self.cache[rid]
            _type.syncronize(ci, refs[rid])

    def _syncModel(self, cacheItem, data):
        self._handleChangeEvent(cacheItem, 'change', {'values': data})

    def _syncCollection(self, cacheItem, data):
        collection = cacheItem.item
        i = len(collection)
        a = [0] * i
        for j in range(i):
            a[j] = collection.atIndex(j)

        def _f(v):
            if v is not None and hasattr(v, 'rid') and v.rid != None:
                # Is the value a reference, get the actual item from cache
                return self.cache[v.rid].item
            return v

        b = list(map(_f, data))

        def _onKeep(id, m, n, idx):
            return None

        def _onAdd(id, n, idx):
            self._handleAddEvent(cacheItem, 'add', {
                'value': data[n],
                idx: idx
            })

        def _onRemove(id, m, idx):
            self._handleRemoveEvent(cacheItem, 'remove', {idx})

        self._patchDiff(a, b, _onKeep, _onAdd, _onRemove)

    def _patchDiff(self, a, b, onKeep, onAdd, onRemove):
        # Do a LCS matric calculation
        # https:#en.wikipedia.org/wiki/Longest_common_subsequence_problem
        t = i = j = aa = bb = None
        s = 0
        m = len(a)
        n = len(b)

        # Trim of matches at the start and end
        while s < m and s < n and a[s] == b[s]:
            s += 1

        if s == m and s == n:
            return
        while s < m and s < n and a[m - 1] == b[n - 1]:
            m -= 1
            n -= 1

        if s > 0 or m < len(a):
            aa = a[s:m]
            m = len(aa)
        else:
            aa = a
        if s > 0 or n < len(b):
            bb = b[s:n]
            n = len(bb)
        else:
            bb = b

    # Create matrix and initialize it
        c = []
        for i in range(m + 1):
            c.append([0] * n + 1)

        for i in range(m - 1):
            for j in range(n - 1):
                c[i + 1][j + 1] = c[i][j] + 1 if aa[i] == bb[j] else max(
                    c[i + 1][j], c[i][j + 1])

        for i in range(s + m, len(a) - 1):
            self.onKeep(a[i], i, i - m + n, i)

        idx = m + s
        i = m
        j = n
        r = 0
        adds = []
        while (True):
            m = i - 1
            n = j - 1
            if (i > 0 and j > 0 and aa[m] == bb[n]):
                self.onKeep(aa[m], m + s, n + s, --idx)
                i -= 1
                j -= 1
            elif j > 0 and (i == 0 or c[i][n] >= c[m][j]):
                adds.append([n, idx, r])
                j -= 1
            elif i > 0 and (j == 0 or c[i][n] < c[m][j]):
                self.onRemove(aa[m], m + s, --idx)
                r += 1
                i -= 1
            else:
                break

        for i in range(s - 1):
            self.onKeep(a[i], i, i, i)

        # Do the adds
        for i in range(len(adds)):
            [n, idx, j] = adds[i]
            self.onAdd(bb[n], n + s, idx - r + j + len(adds) - i)

    async def _unsubscribe(self, ci):
        if not ci.subscribed:
            if (self.stale and self.stale[ci.rid]):
                self._tryDelete(ci)
            return

        self._subscribeReferred(ci)

        try:
            await self._send('unsubscribe', ci.rid, None, None)
            ci.setSubscribed(False)
            self._tryDelete(ci)
        except:
            self.logger.exception('unsubscribe exception')
            self._tryDelete(ci)

    def _subscribeReferred(self, ci):
        ci.subscribed = False
        refs = self._getRefState(ci)
        ci.subscribed = True

        for rid, r in refs.items():
            if r['st'] == stateStale:
                self._subscribe(r['ci'])

    def _handleFailedSubscribe(self, cacheItem, err):
        self.logger.warning('subscription for {} failed'.format(cacheItem.rid))
        cacheItem.setSubscribed(False)
        self._tryDelete(cacheItem)

    async def _reconnect(self):
        await asyncio.sleep(reconnectDelay / 1000)
        if not self.tryConnect:
            return
        self.logger.debug('attempting to reconnect')
        self.connect()

    def _resolvePath(self, url):
        if 'wss:/' in url or 'ws:/' in url:
            return url
        return url.replace('http', 'ws')

    def _traverse(self, ci, refs, cb, state, skipFirst=False):
        # Call callback to get new state to pass to
        # children. If False, we should not traverse deeper
        if not skipFirst:
            state = cb(refs, ci, state)
            if (state == False):
                return

        item = ci.item
        if ci.type == typeCollection:
            for v in item:
                ci = self._getRefItem(v)
                if (ci):
                    self._traverse(refs, ci, cb, state)
        elif ci.type == typeModel:
            for k in item.data:
                ci = self._getRefItem(item.data[k])
                if (ci):
                    self._traverse(refs, ci, cb, state)

    def isResError(self, o):
        return isinstance(o, ResError)
Esempio n. 15
0
class WebSocket:
    def __init__(self,
                 url,
                 header=None,
                 keep_running=True,
                 get_mask_key=None,
                 cookie=None,
                 subprotocols=None):
        self.url = url
        self.ws = WebSocketApp(url,
                               header=header,
                               keep_running=keep_running,
                               get_mask_key=get_mask_key,
                               cookie=cookie,
                               subprotocols=subprotocols)

        # Create streams
        self.rx_on_close = Subject()
        self.rx_on_data = Subject()
        self.rx_on_error = Subject()
        self.rx_on_open = Subject()
        self.rx_on_message = Subject()
        self.rx_on_count_message = Subject()
        self.rx_on_ping = Subject()
        self.rx_on_pong = Subject()

        # Assign callbacks to streams
        self.ws.on_close = lambda x: self.rx_on_close.on_next(x)
        self.ws.on_data = lambda x, s, t, f: self.rx_on_data.on_next(
            (x, s, t, f))
        self.ws.on_error = lambda x, e: self.rx_on_error.on_next((x, e))
        self.ws.on_open = lambda x: self.rx_on_open.on_next(x)
        self.ws.on_message = lambda x, s: self.rx_on_message.on_next((x, s))
        self.ws.on_count_message = lambda x, s, f: self.rx_on_count_message.on_next(
            (x, s, f))
        self.ws.on_ping = lambda: self.rx_on_ping.on_next(None)
        self.ws.on_ping = lambda: self.rx_on_ping.on_next(None)

    def run_forever(self,
                    sockopt=None,
                    sslopt=None,
                    ping_interval=0,
                    ping_timeout=None,
                    http_proxy_host=None,
                    http_proxy_port=None,
                    http_no_proxy=None,
                    http_proxy_auth=None,
                    skip_utf8_validation=False,
                    host=None,
                    origin=None,
                    dispatcher=None,
                    suppress_origin=False,
                    proxy_type=None):
        self.ws.run_forever(sockopt=sockopt,
                            sslopt=sslopt,
                            ping_interval=ping_interval,
                            ping_timeout=ping_timeout,
                            http_proxy_host=http_proxy_host,
                            http_proxy_port=http_proxy_port,
                            http_no_proxy=http_no_proxy,
                            http_proxy_auth=http_proxy_auth,
                            skip_utf8_validation=skip_utf8_validation,
                            host=host,
                            origin=origin,
                            dispatcher=dispatcher,
                            suppress_origin=suppress_origin,
                            proxy_type=proxy_type)

    def close(self):
        self.ws.close()

    def send(self, data, opcode=ABNF.OPCODE_TEXT):
        self.ws.send(data, opcode)
Esempio n. 16
0
class PoloniexSocketed(Poloniex):
    """ Child class of Poloniex with support for the websocket api """
    def __init__(self, *args, **kwargs):
        super(PoloniexSocketed, self).__init__(*args, **kwargs)
        self.socket = WebSocketApp(url="wss://api2.poloniex.com/",
                                   on_open=self.on_open,
                                   on_message=self.on_message,
                                   on_error=self.on_error,
                                   on_close=self.on_close)
        self._t = None
        self._running = False
        self.channels = {
            '1000': {
                'name': 'account',
                'sub': False,
                'callback': self.on_account
            },
            '1002': {
                'name': 'ticker',
                'sub': False,
                'callback': self.on_ticker
            },
            '1003': {
                'name': '24hvolume',
                'sub': False,
                'callback': self.on_volume
            },
            '1010': {
                'name': 'heartbeat',
                'sub': False,
                'callback': self.on_heartbeat
            },
        }
        # add each market to channels list by id
        # (wish there was a cleaner way of doing this...)
        tick = self.returnTicker()
        for market in tick:
            self.channels[str(tick[market]['id'])] = {
                'name': market,
                'sub': False,
                'callback': self.on_market
            }

    def _handle_sub(self, message):
        """ Handles websocket un/subscribe messages """
        chan = str(message[0])
        # skip heartbeats
        if not chan == '1010':
            # Subscribed
            if message[1] == 1:
                # update self.channels[chan]['sub'] flag
                self.channels[chan]['sub'] = True
                self.logger.debug('Subscribed to %s',
                                  self.channels[chan]['name'])
                # return False so no callback trigger
                return False
            # Unsubscribed
            if message[1] == 0:
                # update self.channels[chan]['sub'] flag
                self.channels[chan]['sub'] = False
                self.logger.debug('Unsubscribed to %s',
                                  self.channels[chan]['name'])
                # return False so no callback trigger
                return False
        # return chan name
        return chan

    def on_open(self, *ws):
        for chan in self.channels:
            if self.channels[chan]['sub']:
                self.subscribe(chan)

    def on_message(self, data):
        if not self.jsonNums:
            message = _loads(data, parse_float=str)
        else:
            message = _loads(data,
                             parse_float=self.jsonNums,
                             parse_int=self.jsonNums)
        # catch errors
        if 'error' in message:
            return self.logger.error(message['error'])
        # handle sub/unsub
        chan = self._handle_sub(message)
        if chan:
            # activate chan callback
            # heartbeats are funky
            if not chan == '1010':
                message = message[2]
            self.socket._callback(self.channels[chan]['callback'], message)

    def on_error(self, error):
        self.logger.error(error)

    def on_close(self, *args):
        self.logger.debug('Websocket Closed')

    def on_ticker(self, args):
        self.logger.debug(args)

    def on_account(self, args):
        self.logger.debug(args)

    def on_market(self, args):
        self.logger.debug(args)

    def on_volume(self, args):
        self.logger.debug(args)

    def on_heartbeat(self, args):
        self.logger.debug(args)

    def subscribe(self, chan):
        """ Sends the 'subscribe' command for <chan> """
        # account chan?
        if chan in ['1000', 1000]:
            # sending commands to 'account' requires a key, secret and nonce
            if not self.key or not self.secret:
                raise PoloniexError(
                    "self.key and self.secret needed for 'account' channel")
            payload = {'nonce': self.nonce}
            payload_encoded = _urlencode(payload)
            sign = _new(self.secret.encode('utf-8'),
                        payload_encoded.encode('utf-8'), _sha512)

            self.socket.send(
                _dumps({
                    'command': 'subscribe',
                    'channel': chan,
                    'sign': sign.hexdigest(),
                    'key': self.key,
                    'payload': payload_encoded
                }))
        else:
            self.socket.send(_dumps({'command': 'subscribe', 'channel': chan}))

    def unsubscribe(self, chan):
        """ Sends the 'unsubscribe' command for <chan> """
        # account chan?
        if chan in ['1000', 1000]:
            # sending commands to 'account' requires a key, secret and nonce
            if not self.key or not self.secret:
                raise PoloniexError(
                    "self.key and self.secret needed for 'account' channel")
            payload = {'nonce': self.nonce}
            payload_encoded = _urlencode(payload)
            sign = _new(self.secret.encode('utf-8'),
                        payload_encoded.encode('utf-8'), _sha512)

            self.socket.send(
                _dumps({
                    'command': 'unsubscribe',
                    'channel': chan,
                    'sign': sign.hexdigest(),
                    'key': self.key,
                    'payload': payload_encoded
                }))
        else:
            self.socket.send(
                _dumps({
                    'command': 'unsubscribe',
                    'channel': chan
                }))

    def setCallback(self, chan, callback):
        """ Sets the callback function for <chan> """
        self.channels[chan]['callback'] = callback

    def startws(self, subscribe=[]):
        """
        Run the websocket in a thread, use 'subscribe' arg to subscribe
        to a channel on start
        """
        self._t = Thread(target=self.socket.run_forever)
        self._t.daemon = True
        self._running = True
        # set subscribes
        for chan in self.channels:
            if self.channels[chan]['name'] in subscribe or chan in subscribe:
                self.channels[chan]['sub'] = True
        self._t.start()
        self.logger.info('Websocket thread started')

    def stopws(self, wait=0):
        """ Stop/join the websocket thread """
        self._running = False
        # unsubscribe from subs
        for chan in self.channels:
            if self.channels[chan]['sub'] == True:
                self.unsubscribe(chan)
        sleep(wait)
        try:
            self.socket.close()
        except Exception as e:
            self.logger.exception(e)
        self._t.join()
        self.logger.info('Websocket thread stopped/joined')
Esempio n. 17
0
 def close(self):
     WebSocketApp.close(self)
     self.__client_state = CMDClient.State.DISCONNECTED
Esempio n. 18
0
class UpbitSocket:
    def __init__(self, label):
        self.label = label

        self.ws = WebSocketApp(
            url="wss://api.upbit.com/websocket/v1",
            on_message=lambda ws, msg: self.on_message(ws, msg),
            on_error=lambda ws, msg: self.on_error(ws, msg),
            on_close=lambda ws: self.on_close(ws),
            on_open=lambda ws: self.on_open(ws))

        self.running = False
        self.coins = {}
        for coin in coinList:
            self.coins[coin] = '0'

    def on_message(self, ws, msg):
        msg = json.loads(msg.decode('utf-8'))

        label.master.wm_attributes("-topmost", True)
        if config['realTopmost']:
            label.master.wm_attributes("-topmost", True)

        if msg['code'] in self.coins:
            self.coins[msg['code']] = format(int(msg['trade_price']), ",")

        buf = ''
        for coin in coinList:
            buf = buf + coinList[coin] + "\t" + self.coins[coin] + '\n'

        buf = buf.rstrip("\n")
        self.label['text'] = buf

        # self.ll.master.wm_attributes("-topmost", False)

    #  self.ll.master.wm_attributes("-topmost", True)

    def on_error(self, ws, msg):
        self.running = False

    def on_close(self, ws):
        self.running = False

    def on_open(self, ws):

        sendData = [
            {
                'ticket': 'test'
            },
            {
                'type': 'ticker',
                'codes': [a for a in coinList]
            },
        ]
        ws.send(json.dumps(sendData))

        def check():
            while self.running:
                time.sleep(1)
            self.ws.close()

        th = Thread(target=check, daemon=True)
        th.start()

    def thread(self):
        self.ws.run_forever()

    def start(self):
        th = Thread(target=self.thread, daemon=True)
        th.start()
        self.running = True

    def stop(self):
        self.running = False
Esempio n. 19
0
class WebsocketClient:
    logger: Logger

    _CW_WEBSOCKET_ENDPOINT = 'wss://stream.cryptowat.ch'
    _handlers: dict
    _api_key: str
    _api_secret: str
    _wsa: WebSocketApp
    """
    Simple websocket client for cryptowat.ch.
    Default INFO stdout stream logger will be created if not provided. 
        api_key      API Key, can be obtained at https://cryptowat.ch/account/api-access
        api_secret   API Secret, can be obtained at https://cryptowat.ch/account/api-access
        logger       Replace default stream logger with logging.Logger instance
        debug        Verbose logging level, will log every message. 
    """
    def __init__(self,
                 api_key: str,
                 api_secret: str,
                 logger=None,
                 debug=False):
        self._api_key = api_key
        self._api_secret = api_secret

        self._handlers = {}

        if logger is None:
            self.logger = logging.getLogger('WebsocketClient')
            stream_handler = logging.StreamHandler()
            formatter = logging.Formatter(
                fmt='%(asctime)s: %(levelname)s: %(message)s')
            stream_handler.setFormatter(formatter)
            self.logger.addHandler(stream_handler)
            self.logger.setLevel(logging.INFO if not debug else logging.DEBUG)
        else:
            if isinstance(logger, Logger):
                self.logger = logger
            else:
                raise ValueError(
                    f'logger must be instance of logging.Logger, got {type(logger)}'
                )

        self._wsa = WebSocketApp(self._CW_WEBSOCKET_ENDPOINT,
                                 on_open=self._on_open,
                                 on_message=self._on_message,
                                 on_error=self._on_error,
                                 on_close=self._on_close)

    """
    Register a handler for a specific message type. 
    Multiple handlers for same message type will be stored. 
    Multiple handlers will be called in stored order. 
        message_type    Type of a message (first non-empty field in the message)
        handler         Callable handler for given message type
    """

    def add_handler(self, message_type, handler):
        if message_type in self._handlers:
            if type(self._handlers[message_type]) != list:
                self._handlers[message_type] = [self._handlers[message_type]]
            self._handlers[message_type].append(handler)
        else:
            self._handlers[message_type] = handler

    """
    Unregister one or all handler(s) for a specific message type
        message_type    Type of a message
        handler         Optional callable to remove 
    """

    def remove_handler(self, message_type, handler=None):
        if message_type in self._handlers:
            if handler is None:
                del self._handlers[message_type]
            else:
                if type(self._handlers[message_type]) == list:
                    self._handlers[message_type].remove(handler)
                elif self._handlers[message_type] == handler:
                    del self._handlers[message_type]

    """
    Proxy to websocket send method
    """

    def send(self, data):
        self.logger.debug(f'Sent {data}')
        return self._wsa.send(data)

    """
    Block the thread and run websocket loop.
    Run it from a thread if you prefer non-blocked main thread.
    """

    def run(self):
        self._wsa.run_forever()

    def close(self):
        self._wsa.close()

    def _on_open(self):
        self.logger.info('Open')
        self._do_auth()

    def _do_auth(self):
        m = ClientMessage()
        nonce = str(int(datetime.utcnow().timestamp() * 1_000_000_000))

        key_hash = hmac.new(
            base64.b64decode(self._api_secret),
            f'stream_access;access_key_id={self._api_key};nonce={nonce};'.
            encode(),
            digestmod=hashlib.sha512)

        m.apiAuthentication.token = base64.b64encode(key_hash.digest())
        m.apiAuthentication.nonce = nonce
        m.apiAuthentication.api_key = self._api_key

        self.send(m.SerializeToString())

    def _on_message(self, data):
        self.logger.debug(f'Received {data}')

        if len(data) == 1 and data == b'\x01':
            return

        m = StreamMessage()

        m.ParseFromString(data)
        m_type = m.ListFields()[0][0].name
        message = getattr(m, m.ListFields()[0][0].name)

        if m_type in self._handlers:
            if type(self._handlers[m_type]) == list:
                for handler in self._handlers[m_type]:
                    if callable(handler):
                        try:
                            handler(self, message)
                        except Exception as e:
                            self.logger.error(
                                f'{type(e)} while calling handler for {m_type}: {e}'
                            )
                    else:
                        self.logger.warning(
                            f'Handler for {m_type} is not callable')
            elif callable(self._handlers[m_type]):
                try:
                    self._handlers[m_type](self, message)
                except Exception as e:
                    self.logger.error(
                        f'{type(e)} while calling handler for {m_type}: {e}')
            else:
                self.logger.warning(
                    f'Handler for {m_type} must be callable or list of callable objects, got {type(self._handlers[m_type])}'
                )
        else:
            self.logger.warning(f'No handler for {m_type}')

    def _on_close(self):
        self.logger.info('Connection closed')

    def _on_error(self, error):
        if type(error) == KeyboardInterrupt:
            return

        self.logger.error(f'Error: {type(error)} {error}')
Esempio n. 20
0
class Level2BinanceClient(object):
    def __init__(self, pairs):
        if len(pairs) == 0:
            raise ValueError("The argument passed, 'pairs', is empty!")

        self.pairs = pairs
        self.markets = []
        self._should_run = False
        self._wsapp = None
        self._thread = None
        self._event_buffer = []
        self._last_update = {}

        # Binance for some reason has Bitcoin Cash's ticker as BCC
        self._convert_sym = lambda s: ('BCC'
                                       if s == BCH else ('IOTA'
                                                         if s == IOTA else s))
        self._symbols = [''.join(map(self._convert_sym, p)) for p in pairs]
        self._got_book = {s: False for s in self._symbols}

    def _on_open(self, ws):
        tick_sizes = {}
        step_sizes = {}

        exchange_info_url = 'https://api.binance.com/api/v1/exchangeInfo'
        exchange_info = json.loads(urlopen(exchange_info_url).read().decode())

        for symbol_info in exchange_info['symbols']:
            symbol = symbol_info['symbol']

            if symbol not in self._symbols:
                continue

            filters = symbol_info['filters']
            ftype = 'filterType'
            tick_size = first(filters,
                              lambda f: f[ftype] == 'PRICE_FILTER')['tickSize']
            step_size = first(filters,
                              lambda f: f[ftype] == 'LOT_SIZE')['stepSize']

            tick_sizes[symbol] = Decimal(tick_size.rstrip('0'))
            step_sizes[symbol] = Decimal(step_size.rstrip('0'))

        for i in range(len(self.pairs)):
            pair = self.pairs[i]
            name = '{}/{}'.format(*map(self._convert_sym, pair))
            symbol = self._symbols[i]
            exchange = exchanges.BinanceExchange.instance()
            ts = tick_sizes[symbol]
            ss = step_sizes[symbol]
            market_ = market.BasicMarket(name, pair, symbol, exchange, ts, ss)
            self.markets.append(market_)

    def _on_message(self, ws, msg_str):
        msg = json.loads(msg_str)

        self._event_buffer.append(msg)

        self._process_events()

    def _on_error(self, ws, error):
        print("Binance Websocket Error:", error)
        print("Traceback (most recent call last):")
        traceback.print_tb(error.__traceback__)

        print("Restarting...")

        ws.close()

    def _on_close(self, ws):
        print("Closing Binance Websocket...")

        self.markets.clear()
        self._event_buffer.clear()
        self._last_update.clear()
        self._got_book = {s: False for s in self._symbols}

    def start(self):
        if self._should_run:
            return

        self._should_run = True

        stream_names = [s.lower() + '@depth' for s in self._symbols]
        stream_base = 'wss://stream.binance.com:9443'
        stream_endpoint = ('/ws/' if len(self.pairs) == 1 else
                           '/stream?streams=') + '/'.join(stream_names)
        stream_url = stream_base + stream_endpoint

        def _run():
            while self._should_run:
                self._wsapp = WebSocketApp(stream_url,
                                           on_open=self._on_open,
                                           on_message=self._on_message,
                                           on_error=self._on_error,
                                           on_close=self._on_close)

                self._wsapp.run_forever(sslopt={'cert_reqs': ssl.CERT_NONE})

        self._thread = Thread(target=_run)
        self._thread.start()

    def stop(self):
        if not self.is_running():
            return

        self._should_run = False
        self._wsapp.close()
        self._thread.join()

    def is_running(self):
        return self._thread.isAlive() if self._thread else False

    def _process_events(self):
        i = 0
        while i < len(self._event_buffer):
            msg = self._event_buffer[i] if len(
                self.markets) == 1 else self._event_buffer[i]['data']
            symbol = msg['s']

            market = first(self.markets, lambda m: m.symbol() == symbol)

            first_update = msg['U']
            end_update = msg['u']

            if not self._got_book[symbol]:
                self._reset_book(market)

            if symbol not in self._last_update:
                i += 1
            elif end_update <= self._last_update[symbol]:
                del self._event_buffer[i]
            elif first_update > self._last_update[symbol] + 1:
                print("Skipped on {}: {}!".format('Binance',
                                                  market.disp_name()))
                print(self._last_update[symbol])
                exit()
            else:
                bids = msg['b']
                asks = msg['a']

                tick_size = market.tick_size()
                step_size = market.step_size()

                for bid in bids:
                    price = Decimal(bid[0]).quantize(tick_size)
                    size = Decimal(bid[1]).quantize(step_size)

                    market.update_bid(price, size)

                for ask in asks:
                    price = Decimal(ask[0]).quantize(tick_size)
                    size = Decimal(ask[1]).quantize(step_size)

                    market.update_ask(price, size)

                del self._event_buffer[i]
                self._last_update[symbol] = end_update

    def _reset_book(self, market, limit=500):
        symbol = market.symbol()
        rest_url = 'https://www.binance.com/api/v1/depth?symbol={}&limit={}'.format(
            symbol, limit)

        data_str = urlopen(rest_url).read().decode()
        data = json.loads(data_str)

        print("Got Binance", market.symbol(), "snapshot:", len(data_str),
              "bytes")

        raw_bids = data['bids']
        raw_asks = data['asks']
        last_update = data['lastUpdateId']

        bids = {}
        asks = {}

        for bid in raw_bids:
            price = Decimal(bid[0]).quantize(market.tick_size())
            size = Decimal(bid[1]).quantize(market.step_size())

            bids[price] = size

        for ask in raw_asks:
            price = Decimal(ask[0]).quantize(market.tick_size())
            size = Decimal(ask[1]).quantize(market.step_size())

            asks[price] = size

        market.reset_book(bids, asks)

        self._last_update[symbol] = last_update
        self._got_book[symbol] = True
Esempio n. 21
0
class Connection(Input, Page, Network):
    TIMEOUT = 10.0

    def __init__(self, ws_url: str, *, logger: logging.Logger = None):
        super().__init__()
        self.logger = logger or logging.getLogger(self.__class__.__name__)
        self._seq_no = 0  # type: int
        self._seq_no_lock = RLock()
        self._response_queues = OrderedDict()  # type: Dict[int, Queue]
        self._ws = WebSocketApp(url=ws_url,
                                on_open=self.on_open,
                                on_close=self.on_close,
                                on_message=self.on_message,
                                on_error=self.on_error)

        self._thread_pool = ThreadPoolExecutor()
        threading.Thread(target=self.run, daemon=True).start()
        self.wait_connected()

    def run(self):
        try:
            self._ws.run_forever()
        except Exception as e:
            self.logger.exception(str(e))
            self.safe_close()
        self.logger.info('run_forever finished.')

    def is_connected(self):
        return self._ws.sock and self._ws.sock.connected

    def wait_connected(self, timeout: float = 10.0) -> bool:
        start = time.time()
        while True:
            if self.is_connected():
                return True
            if time.time() - start >= timeout:
                break
            time.sleep(0.001)  # 1 msec
        return False

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.safe_close()

    def safe_close(self):
        if self.is_connected():
            self._ws.close()

    def enable(self, *domains):
        for domain in domains:
            res = self.command('{}.enable'.format(domain))
            self.logger.debug('{}.enable res={}'.format(domain, res))

    def disable(self, *domains):
        for domain in domains:
            self.command('{}.disable'.format(domain))

    def on_open(self, ws):
        self.logger.info('#on_open')

    def on_close(self, ws):
        self.logger.info('#on_close')
        for q in list(self._response_queues.values()):
            q.put(dict(error='no response by on_close'))

    def command(self, method: str, **params) -> dict:
        with self._seq_no_lock:
            self._seq_no += 1
            seq_no = self._seq_no

        data = dict(id=seq_no, method=method, params=params)

        q = Queue()
        self._response_queues[seq_no] = q
        try:
            self.logger.debug('#command={} id={} params={}'.format(method, seq_no, params))
            self._ws.send(json.dumps(data))
            res = q.get(timeout=self.TIMEOUT)
            self.logger.debug('#RES:command={} id={} res={}'.format(method, seq_no, res))
            return res
        except Empty as e:
            if self.is_connected():
                self.logger.exception('ERR:command={} id={} params={}\n{}'.format(method, seq_no, params, str(e)))
                raise
            return dict(error='response timeout by disconnection')
        except Exception as e:
            self.logger.exception('method={} params={}\n{}'.format(method, params, str(e)))
            raise
        finally:
            del self._response_queues[seq_no]

    def on_event(self, data: dict):
        pass

    def on_message(self, ws, message):
        future = self._thread_pool.submit(self._on_message, ws, message)

    def _on_message(self, ws, message):
        self.logger.debug('on_message message={}'.format(message))
        func_tuples = [
            ('DOM', self.on_dom),
            ('Network', self.on_network),
            ('Page', self.on_page),
            ('', self.on_event),  # match all
        ]
        try:
            data = json.loads(message)
            if data.get('method'):
                for _, func in func_tuples:
                    try:
                        func(data)
                    except Exception as e:
                        self.logger.exception(str(e))
            elif data.get('id'):
                try:
                    q = self._response_queues[data['id']]
                    q.put(data)
                except KeyError:
                    pass
        except Exception as e:
            self.logger.exception(str(e))

    def on_error(self, ws, error):
        self.logger.info('#on_error {}'.format(error))
Esempio n. 22
0
class WebsocketClient(Thread):
    _id: int = 0

    ws: WebSocketApp

    def __init__(self,
                 session: Streamlink,
                 url: str,
                 subprotocols: Optional[List[str]] = None,
                 header: Optional[Union[List, Dict]] = None,
                 cookie: Optional[str] = None,
                 sockopt: Optional[Tuple] = None,
                 sslopt: Optional[Dict] = None,
                 host: Optional[str] = None,
                 origin: Optional[str] = None,
                 suppress_origin: bool = False,
                 ping_interval: Union[int, float] = 0,
                 ping_timeout: Optional[Union[int, float]] = None,
                 ping_payload: str = ""):
        if rootlogger.level <= TRACE:
            enableTrace(True, log)

        if not header:
            header = []
        if not any(True for h in header if h.startswith("User-Agent: ")):
            header.append(f"User-Agent: {session.http.headers['User-Agent']}")

        proxy_options = {}
        http_proxy: Optional[str] = session.get_option("http-proxy")
        if http_proxy:
            p = urlparse(http_proxy)
            proxy_options["proxy_type"] = p.scheme
            proxy_options["http_proxy_host"] = p.hostname
            if p.port:  # pragma: no branch
                proxy_options["http_proxy_port"] = p.port
            if p.username:  # pragma: no branch
                proxy_options["http_proxy_auth"] = unquote_plus(
                    p.username), unquote_plus(p.password or "")

        self._reconnect = False
        self._reconnect_lock = RLock()

        self.session = session
        self._ws_init(url, subprotocols, header, cookie)
        self._ws_rundata = dict(sockopt=sockopt,
                                sslopt=sslopt,
                                host=host,
                                origin=origin,
                                suppress_origin=suppress_origin,
                                ping_interval=ping_interval,
                                ping_timeout=ping_timeout,
                                ping_payload=ping_payload,
                                **proxy_options)

        self._id += 1
        super().__init__(name=f"Thread-{self.__class__.__name__}-{self._id}",
                         daemon=True)

    def _ws_init(self, url, subprotocols, header, cookie):
        self.ws = WebSocketApp(url=url,
                               subprotocols=subprotocols,
                               header=header,
                               cookie=cookie,
                               on_open=self.on_open,
                               on_error=self.on_error,
                               on_close=self.on_close,
                               on_ping=self.on_ping,
                               on_pong=self.on_pong,
                               on_message=self.on_message,
                               on_cont_message=self.on_cont_message,
                               on_data=self.on_data)

    def run(self) -> None:
        while True:
            log.debug(f"Connecting to: {self.ws.url}")
            self.ws.run_forever(**self._ws_rundata)
            # check if closed via a reconnect() call
            with self._reconnect_lock:
                if not self._reconnect:
                    return
                self._reconnect = False

    # ----

    def reconnect(self,
                  url: str = None,
                  subprotocols: Optional[List[str]] = None,
                  header: Optional[Union[List, Dict]] = None,
                  cookie: Optional[str] = None,
                  closeopts: Optional[Dict] = None) -> None:
        with self._reconnect_lock:
            # ws connection is not active (anymore)
            if not self.ws.keep_running:
                return
            log.debug("Reconnecting...")
            self._reconnect = True
            self.ws.close(**(closeopts or {}))
            self._ws_init(url=self.ws.url if url is None else url,
                          subprotocols=self.ws.subprotocols
                          if subprotocols is None else subprotocols,
                          header=self.ws.header if header is None else header,
                          cookie=self.ws.cookie if cookie is None else cookie)

    def close(self,
              status: int = STATUS_NORMAL,
              reason: Union[str, bytes] = "",
              timeout: int = 3) -> None:
        self.ws.close(status=status,
                      reason=bytes(reason, encoding="utf-8"),
                      timeout=timeout)
        if self.is_alive():  # pragma: no branch
            self.join()

    def send(self,
             data: Union[str, bytes],
             opcode: int = ABNF.OPCODE_TEXT) -> None:
        return self.ws.send(data, opcode)

    def send_json(self, data: Any) -> None:
        return self.send(json.dumps(data, indent=None, separators=(",", ":")))

    # ----

    # noinspection PyMethodMayBeStatic
    def on_open(self, wsapp: WebSocketApp) -> None:
        log.debug(f"Connected: {wsapp.url}")  # pragma: no cover

    # noinspection PyMethodMayBeStatic
    # noinspection PyUnusedLocal
    def on_error(self, wsapp: WebSocketApp, error: Exception) -> None:
        log.error(error)  # pragma: no cover

    # noinspection PyMethodMayBeStatic
    # noinspection PyUnusedLocal
    def on_close(self, wsapp: WebSocketApp, status: int, message: str) -> None:
        log.debug(f"Closed: {wsapp.url}")  # pragma: no cover

    def on_ping(self, wsapp: WebSocketApp, data: str) -> None:
        pass  # pragma: no cover

    def on_pong(self, wsapp: WebSocketApp, data: str) -> None:
        pass  # pragma: no cover

    def on_message(self, wsapp: WebSocketApp, data: str) -> None:
        pass  # pragma: no cover

    def on_cont_message(self, wsapp: WebSocketApp, data: str,
                        cont: Any) -> None:
        pass  # pragma: no cover

    def on_data(self, wsapp: WebSocketApp, data: str, data_type: int,
                cont: Any) -> None:
        pass  # pragma: no cover
Esempio n. 23
0
class XSocketsClient(object):
	def __init__(self, url, onerror = None, onopen = None, onclose = None):
		super(XSocketsClient, self).__init__()
		self.onerror = onerror
		self.onopen = onopen
		self.subscriptions = Subscriptions()
		self.webSocket = None
		self.bind(XSockets.Events.open, self._open_event_handler, opts = {"skip": True})

		thread.start_new_thread(self.start, (url, onopen, onclose))

	def _open_event_handler(self, data):
		log.DEBUG(data)
		data[u"clientType"] = u"RFC6455"
		self.connection = data
		self.XSocketsClientStorageGuid = data["StorageGuid"]
		for sub in self.subscriptions.getAll():
			for callback in sub.callbacks:
				if sub.name and callback.state.get("options", {}).get("skip", False): continue
				self.trigger(Message(
					XSockets.Events.pubSub.subscribe,
					{"Event": sub.name, "Confim": False}))
		self.dispatchEvent(XSockets.Events.bindings.completed, self.subscriptions.getAll())
		
		if self.onopen: self.onopen()


	def start(self, url, onopen, onclose):
		self.webSocket = ConnectionManager(url,
			on_message = self._on_message,
			on_error = self.print_error,
			on_close = onclose)
		self.webSocket.run_forever()

	def print_error(self, *args, **kwargs):
	  print args, kwargs

	def __del__(self, *args):
		if self.webSocket is not None:
			self.webSocket.close()

	def _on_message(self, ws, message):
		cont = json.loads(message)
		log.DEBUG(cont)
		self.dispatchEvent(cont["event"], cont["data"])
		if cont["event"] == XSockets.Events.onError:
			if self.onerror: self.onerror(message)
			self.dispatchEvent(cont["event"], cont["data"])

	def bind(self, event, fn, opts = {}, callback = None):
		state = {
			"options": opts,
			"ready": self.webSocket is not None and self.webSocket.sock is not None,
			"confim": callback is not None
		}

		log.DEBUG("%s - %s" %(event, fn))
		if state["ready"]:
			self.trigger(Message(
				XSockets.Events.pubSub.subscribe,
				{"Event": event, "Confim": state["confim"]}))

		if isinstance(fn, list):
			for f in fn:
				self.subscriptions.add(event, f, state)
		else:
			self.subscriptions.add(event, fn, state)

		return self
	on = bind
	subscribe = bind

	def trigger(self, event, json = {}, callback = None):
		if isinstance(event, Message):
			message = event
		else:
			event = event.lower()
			message = Message(event, json)

		log.DEBUG(message.to_json())
		self.webSocket.send(message.to_json())
		if callback is not None: callback()
		return self

	publish = trigger
	emit = trigger

	def dispatchEvent(self, event, message):
		if self.subscriptions.get(event) is None: return
		self.subscriptions.fire(event, json.loads(message))
	
	def send(self, data, event):
		mes = Message(event, data)
		log.DEBUG(mes.to_json())
		self.webSocket.send(mes.to_json())
Esempio n. 24
0
class Binary(mt_helper.MultiThreadWSHelper):
    '''
    Wrapper of Binary.com API
    Provides unathorized operations. Authorized scope is in binary_account module
    '''

    def __init__(self, ):

        super().__init__()
        self.url = bin_api.get_binary_url()
        self.app = WebSocketApp(url=self.url,
                                on_open = lambda ws: self.on_app_open(ws),
                                on_close = lambda ws: self.on_app_close(ws),
                                on_message = lambda ws, msg: self.on_app_msg(ws, msg),
                                on_error = lambda ws, err: self.on_app_error(ws, err),
                                on_ping = lambda ws: self.on_app_ping(ws))

        self.reconnect = True


    def send_request(self, request):
        '''
        Sends request considering sharing between threads.
        Ideally it should be placed into helper,
        but app (WebSocketApp) defined here
        '''

        with self.ws_lock:
            self.app.send(request)

    def open_app(self):
        Thread(target=self.app.run_forever).start()

    def close_app(self):
        del self.responses
        self.reconnect = False
        self.app.close()

    # Websockets methods:

    def on_app_open(self):
        '''
        We have nothing to do here
        :return:
        '''
        pass


    def on_app_msg(self, ws, msg):

        try:
            resp = json.loads(msg)
            self.add_response(resp)

            id_ = resp['req_id']
            if self.in_events(id_):
                self.set_event(id_)

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(str(ex_type)+'\n'+'\n'.join(traceback.format_tb(ex_tb)))

        ready = True # line for debug

    def on_app_close(self, ws):

        if self.reconnect:
            self.app = WebSocketApp(url=self.url,
                                on_open = lambda ws: self.on_app_open(ws),
                                on_close = lambda ws: self.on_app_close(ws),
                                on_message = lambda ws, msg: self.on_app_msg(ws, msg),
                                on_error = lambda ws, err: self.on_app_error(ws, err),
                                on_ping = lambda ws: self.on_app_ping(ws))
            Thread(target=self.app.run_forever).start()

    def on_app_error(self, ws, error):
        pass

    def on_app_ping(self, ws):
        pass

    def get_history(self, asset = 'frxEURUSD', granularity=3600,
                    count=50, subscribe=0, style='candles'):
        '''
        Returns list of candles as tuple:
        tuple = (date, open, high, low, close, subscription_id)
        :return: list
        '''

        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)
        self.send_request(bin_api.get_tick_history_json(symbol=asset,
                                                        style=style,
                                                        granularity=granularity,
                                                        count=count,
                                                        subscribe=subscribe,
                                                        req_id=curID))

        # TODO: subscribe

        curEvent.wait()

        response = self.get_response(curID)
        response = response['candles']
        return [(r['epoch'], r['open'], r['high'], r['low'], r['close'])
                    for r in response
            ]

    def get_price_proposal(self, asset = 'frxEURUSD', amount = 1,
                          duration = 60, duration_unit = 'm', type='CALL'):
        '''
        Returns price proposal for buying an option as dict
        Keys:
            - date_start
            - proposal_id
            - description
            - payout

        :return: dict
        '''

        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)
        self.send_request(bin_api.get_price_proposal_json(amount=amount,
                                                          type=type,
                                                          duration=duration,
                                                          duration_unit=duration_unit,
                                                          symbol=asset,
                                                          req_id=curID))

        curEvent.wait()

        response = self.get_response(curID)
        if 'error' in response:
            return {
                'date_start' : '',
                'proposal_id' : '',
                'description' : response['error']['message'],
                'payout' : 0,
                'error' : True,
                'err_msg' : response['error']['message']
            }

        return {
            'date_start' : response['proposal']['date_start'] if 'date_start' in response['proposal'] else '',
            'proposal_id' : response['proposal']['id'],
            'description' : response['proposal']['longcode'],
            'payout' : response['proposal']['payout'],
            'error' : False,
            'err_msg' : ''
        }


    def forget_subscription(self, subscription_id, event_to_remove = None):
        '''
        Makes request to stop receive events by subscription
        :return:
        '''

        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)
        self.send_request(bin_api.get_forget_stream_json(subscription_id, req_id=curID))

        curEvent.wait()

        if not event_to_remove is None:
            self.remove_event(event_to_remove)
class Client:
    Ws = None
    IsOpen = False

    def __init__(self,
                 url,
                 onWsOpen=None,
                 onWsMsg=None,
                 onWsData=None,
                 onWsClose=None,
                 onWsError=None,
                 headerArray=None):
        def OnOpen(ws):
            self.IsOpen = True
            if onWsOpen:
                onWsOpen(self)

        def OnMsg(ws, msg):
            if onWsMsg:
                onWsMsg(self, msg)

        def onClosed(ws):
            if onWsClose:
                onWsClose(self)

        def OnError(ws, msg):
            if onWsError:
                onWsError(self, msg)

        def OnData(ws, buffer, type, continueFlag):
            if onWsData:
                onWsData(self, buffer, type == websocket.ABNF.OPCODE_BINARY)

        self.Ws = WebSocketApp(url,
                               on_message=OnMsg,
                               on_close=onClosed,
                               on_error=OnError,
                               on_data=OnData,
                               header=headerArray)
        self.Ws.on_open = OnOpen

    def RunUntilClosed(self):
        # Note we must set the ping_interval and ping_timeout or we won't get a multithread
        # safe socket... python. >.>
        self.Ws.run_forever(skip_utf8_validation=True,
                            ping_interval=600,
                            ping_timeout=120)

    def RunAsync(self):
        t = threading.Thread(target=self.RunUntilClosed, args=())
        t.daemon = True
        t.start()

    def Close(self):
        self.IsOpen = False
        if self.Ws:
            self.Ws.close()
            self.Ws = None

    def Send(self, msgBytes, isData):
        if self.Ws:
            if isData:
                self.Ws.send(msgBytes, opcode=websocket.ABNF.OPCODE_BINARY)
            else:
                self.Ws.send(msgBytes, opcode=websocket.ABNF.OPCODE_TEXT)
Esempio n. 26
0
class Slack(Base):
    def __init__(self, token: str = "", plugins: Sequence[PluginConfig] = None, max_workers: int = None) -> None:

        super().__init__(plugins=plugins, max_workers=max_workers)

        self.client = self.setup_client(token=token)
        self.message_id = 0
        self.ws = None

    def setup_client(self, token: str) -> SlackClient:
        return SlackClient(token=token)

    def connect(self) -> None:
        try:
            response = self.client.get("rtm.start")
        except Exception as e:
            raise SarahSlackException("Slack request error on /rtm.start. %s" % e)
        else:
            if "url" not in response:
                raise SarahSlackException("Slack response did not contain connecting url. %s" % response)

            self.ws = WebSocketApp(
                response["url"],
                on_message=self.message,
                on_error=self.on_error,
                on_open=self.on_open,
                on_close=self.on_close,
            )
            self.ws.run_forever()

    def add_schedule_job(self, command: Command) -> None:
        if "channels" not in command.config:
            logging.warning("Missing channels configuration for schedule job. %s. " "Skipping." % command.module_name)
            return

        def job_function() -> None:
            ret = command.execute()
            if isinstance(ret, SlackMessage):
                for channel in command.config["channels"]:
                    # TODO Error handling
                    data = dict({"channel": channel})
                    data.update(ret.to_request_params())
                    self.client.post("chat.postMessage", data=data)
            else:
                for channel in command.config["channels"]:
                    self.enqueue_sending_message(self.send_message, channel, str(ret))

        job_id = "%s.%s" % (command.module_name, command.name)
        logging.info("Add schedule %s" % id)
        self.scheduler.add_job(job_function, "interval", id=job_id, minutes=command.config.get("interval", 5))

    @concurrent
    def message(self, _: WebSocketApp, event: str) -> None:
        decoded_event = json.loads(event)

        if "ok" in decoded_event and "reply_to" in decoded_event:
            # https://api.slack.com/rtm#sending_messages
            # Replies to messages sent by clients will always contain two
            # properties: a boolean ok indicating whether they succeeded and
            # an integer reply_to indicating which message they are in response
            # to.
            if decoded_event["ok"] is False:
                # Something went wrong with the previous message
                logging.error(
                    "Something went wrong with the previous message. "
                    "message_id: %d. error: %s" % (decoded_event["reply_to"], decoded_event["error"])
                )
            return

        # TODO organize
        type_map = {
            "hello": {
                "method": self.handle_hello,
                "description": "The client has successfully connected " "to the server",
            },
            "message": {"method": self.handle_message, "description": "A message was sent to a channel"},
            "user_typing": {"description": "A channel member is typing a " "message"},
        }

        if "type" not in decoded_event:
            # https://api.slack.com/rtm#events
            # Every event has a type property which describes the type of
            # event.
            logging.error("Given event doesn't have type property. %s" % event)
            return

        if decoded_event["type"] not in type_map:
            logging.error("Unknown type value is given. %s" % event)
            return

        logging.debug(
            "%s: %s. %s"
            % (decoded_event["type"], type_map[decoded_event["type"]].get("description", "NO DESCRIPTION"), event)
        )

        if "method" in type_map[decoded_event["type"]]:
            type_map[decoded_event["type"]]["method"](decoded_event)
            return

    def handle_hello(self, _: Dict) -> None:
        logging.info("Successfully connected to the server.")

    def handle_message(self, content: Dict) -> Optional[Future]:
        # content
        # {
        #     "type":"message",
        #     "channel":"C06TXXXX",
        #     "user":"******",
        #     "text":".bmw",
        #     "ts":"1438477080.000004",
        #     "team":"T06TXXXXX"
        # }
        required_props = ("type", "channel", "user", "text", "ts")
        missing_props = [p for p in required_props if p not in content]

        if missing_props:
            logging.error("Malformed event is given. Missing %s. %s" % (", ".join(missing_props), content))
            return

        ret = self.respond(content["user"], content["text"])
        if isinstance(ret, SlackMessage):
            # TODO Error handling
            data = dict({"channel": content["channel"]})
            data.update(ret.to_request_params())
            self.client.post("chat.postMessage", data=data)
        elif isinstance(ret, str):
            return self.enqueue_sending_message(self.send_message, content["channel"], ret)

    def on_error(self, _: WebSocketApp, error) -> None:
        logging.error(error)

    def on_open(self, _: WebSocketApp) -> None:
        logging.info("connected")

    def on_close(self, _: WebSocketApp) -> None:
        logging.info("closed")

    def send_message(self, channel: str, text: str, message_type: str = "message") -> None:
        params = {"channel": channel, "text": text, "type": message_type, "id": self.next_message_id()}
        self.ws.send(json.dumps(params))

    def next_message_id(self) -> int:
        # https://api.slack.com/rtm#sending_messages
        # Every event should have a unique (for that connection) positive
        # integer ID. All replies to that message will include this ID.
        self.message_id += 1
        return self.message_id

    def stop(self) -> None:
        super().stop()
        logging.info("STOP SLACK INTEGRATION")
        self.ws.close()
Esempio n. 27
0
class WSConnector:
    class REID:
        def __init__(self):
            self._next = 0

        def __next__(self):
            n = self._next
            self._next += 1
            return n

        def __iter__(self):
            return self

    def __init__(self,
                 username: str,
                 token: str,
                 address: str,
                 on_msg=None,
                 ignore_ssl_cert=False):
        self.username = username
        self.token = token
        self.address = address
        self.on_msg = on_msg
        self.ws = None
        self.lock = Lock()
        self.reid = self.REID()
        self.running = False
        self.ignore_ssl_cert = ignore_ssl_cert
        setdefaulttimeout(60)

    def send(self, data):
        with self.lock:
            self.ws.send(packb(self.construct_package(data),
                               use_bin_type=True),
                         opcode=ABNF.OPCODE_BINARY)

    def start(self):
        self.stop()
        self.ws = WebSocketApp(
            self.address,
            on_message=None if self.on_msg is None else self._handle_msg,
            on_open=self._ready,
            on_error=self._fail)
        self.lock.acquire()
        kwargs = {
            "sslopt": {
                "cert_reqs": CERT_NONE
            }
        } if self.ignore_ssl_cert else None
        Thread(target=self.ws.run_forever, kwargs=kwargs).start()

    def _fail(self, ws, err):
        self.lock.release()
        raise err

    def stop(self):
        if self.ws is not None:
            with self.lock:
                print("Closing the connection.")
                self.running = False
                self.ws.close()
                self.ws = None

    def _ready(self, ws):
        print(f"Connected to {self.address}.")
        self.running = True
        self.lock.release()

    def _handle_msg(self, ws, msg):
        if isinstance(msg, bytes):
            msg = unpackb(msg)
        self.on_msg(msg)

    def construct_package(self, payload_data):
        return {
            'REID': next(self.reid),
            'AUTH': {
                'USER': self.username,
                'TOKEN': self.token
            },
            'VERB': 'PUT',
            'PATH': ['user', self.username, 'model'],
            'META': {},
            'PAYL': payload_data
        }
Esempio n. 28
0
 def _reconnect(self, ws: WebSocketApp) -> None:
     assert ws is not None, '_reconnect should only be called with an existing ws'
     if ws is self._ws:
         self._ws = None
         ws.close()
         self.connect()
Esempio n. 29
0
class BinaryAccount(mt_helper.MultiThreadWSHelper):
    '''
        Make all operations with binary.com account by given apiToken
        You can:
            - read portfolio
            - read statement
            - buy/sell contracts

        all methods can be called from different threads.

        Each request has unique ID for internal usage.
        And each response has the same ID.
    '''
    def __init__(self, apiToken):

        super().__init__()

        self.apiToken = apiToken
        self.url = bin_api.get_binary_url()
        self.app = WebSocketApp(
            url=self.url,
            on_open=lambda ws: self.on_app_open(ws),
            on_close=lambda ws: self.on_app_close(ws),
            on_message=lambda ws, msg: self.on_app_msg(ws, msg),
            on_error=lambda ws, err: self.on_app_error(ws, err),
            on_ping=lambda ws: self.on_app_ping(ws))

        self.authorized = False
        '''
            If connection closes by any reason, BinaryAccount will create new application.
            If the app is closed and reconnection is not required, it won't.
        '''
        self.reconnect = True

    def send_request(self, request):
        '''
        Sends request considering sharing between threads.
        Ideally it should be placed into helper,
        but app (WebSocketApp) defined here
        '''

        with self.ws_lock:
            self.app.send(request)

    def open_app(self):
        Thread(target=self.app.run_forever).start()
        while not self.authorized:
            pass

    def close_app(self):
        del self.responses
        self.reconnect = False
        self.app.close()

    # Websockets methods:

    def on_app_open(self, ws):

        # Authorize on open
        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)
        self.send_request(
            bin_api.get_authorize_json(self.apiToken, req_id=curID))

    def on_app_msg(self, ws, msg):

        try:
            resp = json.loads(msg)
            self.add_response(resp)

            if 'authorize' in resp:
                self.authorized = True

            id_ = resp['req_id']
            if self.in_events(id_):
                self.set_event(id_)

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

        ready = True  # line for debug

    def on_app_close(self, ws):

        if self.reconnect:
            self.app = WebSocketApp(
                url=self.url,
                on_open=lambda ws: self.on_app_open(ws),
                on_close=lambda ws: self.on_app_close(ws),
                on_message=lambda ws, msg: self.on_app_msg(ws, msg),
                on_error=lambda ws, err: self.on_app_error(ws, err),
                on_ping=lambda ws: self.on_app_ping(ws))
            Thread(target=self.app.run_forever).start()

    def on_app_error(self, ws, error):
        pass

    def on_app_ping(self, ws):
        pass

    # Account manipulation methods

    def get_balance(self):

        try:
            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)
            self.send_request(bin_api.get_balance_json(req_id=curID))

            curEvent.wait()

            response = self.get_response(curID)

            return response['balance']['balance']

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

    def get_portfolio(self):
        '''
        Returns current opened positions as list of dict
        Each dict has keys:
            - price
            - payout
            - contract_id
            - contract_type
            - date_start
            - date_end
            - description
            - symbol
        :return: list
        '''

        try:

            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)
            self.send_request(bin_api.get_portfolio_json(req_id=curID))

            curEvent.wait()

            response = self.get_response(curID)

            return [{
                'price': r['buy_price'],
                'payout': r['payout'],
                'contract_id': r['contract_id'],
                'contract_type': r['contract_type'],
                'date_start': r['date_start'],
                'date_end': r['expiry_time'],
                'description': r['longcode'],
                'symbol': r['symbol']
            } for r in response['portfolio']['contracts']]

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

    def get_login_history(self, limit=25):
        '''
        Returns list of string with description of login
        :param limit: max number of login notes
        :return: list
        '''

        try:

            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)
            self.send_request(bin_api.get_login_history_json(req_id=curID))

            curEvent.wait()

            response = self.get_response(curID)

            return [r['environment'] for r in response['login_history']]

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

    def get_profit_table(self, limit=10, date_from=None, date_to=None):
        '''
        Returns list of dict.
        Keys:
            - price
            - potential_payout
            - sell_price
            - contract_id
            - description
            - purchase_time
            - sell_time
        :param limit:
        :param date_from: int
        :param date_to: int
        :return:
        '''

        try:

            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)

            self.send_request(
                bin_api.get_profit_table_json(limit=limit,
                                              date_from=date_from,
                                              date_to=date_to,
                                              req_id=curID))
            curEvent.wait()

            response = self.get_response(curID)

            return [{
                'price': r['buy_price'],
                'potential_payout': r['payout'],
                'sell_price': r['sell_price'],
                'contract_id': r['contract_id'],
                'purchase_time': r['purchase_time'],
                'sell_time': r['sell_time'],
                'description': r['longcode']
            } for r in response['profit_table']['transactions']]

        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

    def sell_contract(self, contract_id, price=0):
        '''
        Sells specified contract and shows the result as dict
        Keys:
            - balance_after
            - sold_for
            OR
            - error : err_msg
        :param contract_id:
        :param price:
        :return:
        '''

        try:

            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)
            self.send_request(
                bin_api.get_sell_contract_json(contract_id,
                                               price,
                                               req_id=curID))

            curEvent.wait()

            response = self.get_response(curID)

            if not 'error' in response:
                return {
                    'balance_after': response['sell']['balance_after'],
                    'sold_for': response['sell']['sold_for']
                }
            else:
                return {'error': response['error']['message']}
        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))

    def buy_contract(self,
                     proposal_id=None,
                     amount=1,
                     type='CALL',
                     duration=15,
                     duration_unit='m',
                     symbol='frxEURUSD'):
        '''
        Buys contract by proposal_id (the object where all parameters were already passed)
        or by given parameters.
        To use custom parameters proposal_id HAVE TO BE None

        Returns dict with details
        Keys:
            - balance_after
            - buy_price
            - contract_id
            - description
            - payout
            - start_time
        :return: dict
        '''

        try:
            curID = self.get_next_req_id()
            curEvent = Event()
            self.add_event(curID, curEvent)

            if not proposal_id is None:
                self.send_request(
                    bin_api.get_buy_contract_json(proposal_id=proposal_id,
                                                  price=amount,
                                                  proposal_parameters=None,
                                                  req_id=curID))
            else:
                proposal_parameters = bin_api.get_price_proposal_dict(
                    amount=amount,
                    type=type,
                    duration=duration,
                    duration_unit=duration_unit,
                    symbol=symbol)
                self.send_request(
                    bin_api.get_buy_contract_json(
                        proposal_id=1,
                        proposal_parameters=proposal_parameters,
                        price=amount,
                        req_id=curID))

            curEvent.wait()

            response = self.get_response(curID)

            if 'error' in response:
                return {'error': response['error']['message']}
            else:
                return {
                    'balance_after': response['buy']['balance_after'],
                    'buy_price': response['buy']['buy_price'],
                    'contract_id': response['buy']['contract_id'],
                    'description': response['buy']['longcode'],
                    'payout': response['buy']['payout'],
                    'start_time': response['buy']['start_time']
                }
        except:
            ex_type, ex_val, ex_tb = sys.exc_info()
            logging.error(
                str(ex_type) + '\n' + '\n'.join(traceback.format_tb(ex_tb)))
            print(ex_val)

    def get_price_proposal(self, contract_id, subscribe=0):
        '''
        Returns information about opened position as dict
        Keys:
            - buy_price
            - current_spot
            - entry_spot
            - contract_type
            - symbol
            - is_valid_to_sell
            - profit
            - profit_percentage
            - contract_id
            - description

            - subscription_id
            - req_id

        Last two keys are only available for subscriptions.
        This allows to forget subscription and then remove event
        :return: dict
        '''

        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)

        self.send_request(
            bin_api.get_contract_proposal_json(contract_id=contract_id,
                                               subscribe=subscribe,
                                               req_id=curID))

        if subscribe == 1:

            saved_response = None

            while True:

                # if subscription had been removed
                if not self.in_events(curID):
                    return

                curEvent.wait(10)
                response = self.get_response(curID)
                resp = response['proposal_open_contract']

                curEvent.clear()

                if response != saved_response:
                    saved_response = response
                    yield {
                        'buy_price': resp['buy_price'],
                        'current_spot': resp['current_spot'],
                        'entry_spot': resp['entry_spot'],
                        'contract_type': resp['contract_type'],
                        'symbol': resp['display_name'],
                        'is_valid_to_sell': resp['is_valid_to_sell'],
                        'profit': resp['profit'],
                        'profit_percentage': resp['profit_percentage'],
                        'contract_id': resp['contract_id'],
                        'description': resp['longcode'],
                        'subscription_id': response['subscription']['id'],
                        'req_id': curID
                    }

        else:

            curEvent.wait()

            response = self.get_response(curID)
            resp = response['proposal_open_contract']

            return {
                'buy_price': resp['buy_price'],
                'current_spot': resp['current_spot'],
                'entry_spot': resp['entry_spot'],
                'contract_type': resp['contract_type'],
                'symbol': resp['display_name'],
                'is_valid_to_sell': resp['is_valid_to_sell'],
                'profit': resp['profit'],
                'profit_percentage': resp['profit_percentage'],
                'contract_id': resp['contract_id'],
                'description': resp['longcode']
            }

    def forget_subscription(self, subscription_id, event_to_remove=None):
        '''
        Makes request to stop receive events by subscription
        :return:
        '''

        curID = self.get_next_req_id()
        curEvent = Event()
        self.add_event(curID, curEvent)
        self.send_request(
            bin_api.get_forget_stream_json(subscription_id, req_id=curID))

        curEvent.wait()

        if not event_to_remove is None:
            self.remove_event(event_to_remove)
Esempio n. 30
0
class XSocketsClient(object):
    def __init__(self, url, onerror=None, onopen=None, onclose=None):
        super(XSocketsClient, self).__init__()
        self.onerror = onerror
        self.onopen = onopen
        self.subscriptions = Subscriptions()
        self.webSocket = None
        self.bind(XSockets.Events.open,
                  self._open_event_handler,
                  opts={"skip": True})

        thread.start_new_thread(self.start, (url, onopen, onclose))

    def _open_event_handler(self, data):
        log.DEBUG(data)
        data[u"clientType"] = u"RFC6455"
        self.connection = data
        self.XSocketsClientStorageGuid = data["StorageGuid"]
        for sub in self.subscriptions.getAll():
            for callback in sub.callbacks:
                if sub.name and callback.state.get("options", {}).get(
                        "skip", False):
                    continue
                self.trigger(
                    Message(XSockets.Events.pubSub.subscribe, {
                        "Event": sub.name,
                        "Confim": False
                    }))
        self.dispatchEvent(XSockets.Events.bindings.completed,
                           self.subscriptions.getAll())

        if self.onopen: self.onopen()

    def start(self, url, onopen, onclose):
        self.webSocket = ConnectionManager(url,
                                           on_message=self._on_message,
                                           on_error=self.print_error,
                                           on_close=onclose)
        self.webSocket.run_forever()

    def print_error(self, *args, **kwargs):
        print(args, kwargs)

    def __del__(self, *args):
        if self.webSocket is not None:
            self.webSocket.close()

    def _on_message(self, ws, message):
        cont = json.loads(message)
        log.DEBUG(cont)
        self.dispatchEvent(cont["event"], cont["data"])
        if cont["event"] == XSockets.Events.onError:
            if self.onerror: self.onerror(message)
            self.dispatchEvent(cont["event"], cont["data"])

    def bind(self, event, fn, opts={}, callback=None):
        state = {
            "options": opts,
            "ready": self.webSocket is not None
            and self.webSocket.sock is not None,
            "confim": callback is not None
        }

        log.DEBUG("%s - %s" % (event, fn))
        if state["ready"]:
            self.trigger(
                Message(XSockets.Events.pubSub.subscribe, {
                    "Event": event,
                    "Confim": state["confim"]
                }))

        if isinstance(fn, list):
            for f in fn:
                self.subscriptions.add(event, f, state)
        else:
            self.subscriptions.add(event, fn, state)

        return self

    on = bind
    subscribe = bind

    def trigger(self, event, json={}, callback=None):
        if isinstance(event, Message):
            message = event
        else:
            event = event.lower()
            message = Message(event, json)

        log.DEBUG(message.to_json())
        self.webSocket.send(message.to_json())
        if callback is not None: callback()
        return self

    publish = trigger
    emit = trigger

    def dispatchEvent(self, event, message):
        if self.subscriptions.get(event) is None: return
        self.subscriptions.fire(event, json.loads(message))

    def send(self, data, event):
        mes = Message(event, data)
        log.DEBUG(mes.to_json())
        self.webSocket.send(mes.to_json())
Esempio n. 31
0
class MidiHandler:
    # Initializes the handler class
    def __init__(self,
                 config_path="config.json",
                 ws_server="localhost",
                 ws_port=4444):
        # Setting up logging first and foremost
        self.log = get_logger("midi_to_obs")

        # Internal service variables
        self._action_buffer = []
        self._action_counter = 2

        self.log.debug("Trying to load config file  from %s" % config_path)
        self.db = TinyDB(config_path, indent=4)
        result = self.db.search(Query().type.exists())
        if not result:
            self.log.critical("Config file %s doesn't exist or is damaged" %
                              config_path)
            # ENOENT (No such file or directory)
            exit(2)

        self.log.info("Successfully parsed config file")
        port_name = str(result[0]["value"])

        self.log.debug("Retrieved MIDI port name `%s`" % port_name)
        del result

        try:
            self.log.debug("Attempting to open midi port")
            self.port = mido.open_input(name=port_name,
                                        callback=self.handle_midi_input)
        except:
            self.log.critical(
                "The midi device %s is not connected or has a different name" %
                port_name)
            self.log.critical(
                "Please plug the device in or run setup.py again and restart this script"
            )
            # EIO 5 (Input/output error)
            exit(5)

        self.log.info("Successfully initialized midi port `%s`" % port_name)
        del port_name

        # Properly setting up a Websocket client
        self.log.debug("Attempting to connect to OBS using websocket protocol")
        self.obs_socket = WebSocketApp("ws://%s:%d" % (ws_server, ws_port))
        self.obs_socket.on_message = self.handle_obs_message
        self.obs_socket.on_error = self.handle_obs_error
        self.obs_socket.on_close = self.handle_obs_close
        self.obs_socket.on_open = self.handle_obs_open

    def handle_midi_input(self, message):
        self.log.debug("Received %s message from midi: %s" %
                       (message.type, message))

        if message.type == "note_on":
            return self.handle_midi_button(message.type, message.note)

        # `program_change` messages can be only used as regular buttons since
        # they have no extra value, unlike faders (`control_change`)
        if message.type == "program_change":
            return self.handle_midi_button(message.type, message.program)

        if message.type == "control_change":
            return self.handle_midi_fader(message.control, message.value)

    def handle_midi_button(self, type, note):
        query = Query()
        results = self.db.search((query.msg_type == type)
                                 & (query.msgNoC == note))

        if not results:
            self.log.debug("Cound not find action for note %s", note)
            return

        for result in results:
            if self.send_action(result):
                break

    def handle_midi_fader(self, control, value):
        query = Query()
        results = self.db.search((query.msg_type == "control_change")
                                 & (query.msgNoC == control))

        if not results:
            self.log.debug("Cound not find action for fader %s", control)
            return

        for result in results:
            input_type = result["input_type"]
            action = result["action"]

            if input_type == "button":
                if value == 127 and not self.send_action(result):
                    continue
                break

            if input_type == "fader":
                command = result["cmd"]
                scaled = map_scale(value, 0, 127, result["scale_low"],
                                   result["scale_high"])

                if command == "SetSourcePosition" or command == "SetSourceScale":
                    self.obs_socket.send(action % scaled)
                    break

                # Super dirty hack but @AlexDash says that it works
                # @TODO: find an explanation _why_ it works
                if command == "SetVolume":
                    # Yes, this literally raises a float to a third degree
                    self.obs_socket.send(action % scaled**3)
                    break

                if command == "SetSourceRotation" or command == "SetTransitionDuration" or command == "SetSyncOffset":
                    self.obs_socket.send(action % int(scaled))
                    break

    def handle_obs_message(self, message):
        self.log.debug("Received new message from OBS")
        payload = json.loads(message)

        self.log.debug("Successfully parsed new message from OBS")

        if "error" in payload:
            self.log.error("OBS returned error: %s" % payload["error"])
            return

        message_id = payload["message-id"]

        self.log.debug("Looking for action with message id `%s`" % message_id)
        for action in self._action_buffer:
            (buffered_id, template, kind) = action

            if buffered_id != int(payload["message-id"]):
                continue

            del buffered_id
            self.log.info("Action `%s` was requested by OBS" % kind)

            if kind == "ToggleSourceVisibility":
                # Dear lain, I so miss decent ternary operators...
                invisible = "false" if payload["visible"] else "true"
                self.obs_socket.send(template % invisible)
            elif kind == "ReloadBrowserSource":
                source = payload["sourceSettings"]["url"]
                target = source[0:-1] if source[-1] == '#' else source + '#'
                self.obs_socket.send(template % target)

            self.log.debug("Removing action with message id %s from buffer" %
                           message_id)
            self._action_buffer.remove(action)
            break

    def handle_obs_error(self, ws, error=None):
        # Protection against potential inconsistencies in `inspect.ismethod`
        if error is None and isinstance(ws, BaseException):
            error = ws

        if isinstance(error, (KeyboardInterrupt, SystemExit)):
            self.log.info("Keyboard interrupt received, gracefully exiting...")
            self.close(teardown=True)
        else:
            self.log.error("Websocket error: %" % str(error))

    def handle_obs_close(self, ws):
        self.log.error("OBS has disconnected, timed out or isn't running")
        self.log.error("Please reopen OBS and restart the script")

    def handle_obs_open(self, ws):
        self.log.info("Successfully connected to OBS")

    def send_action(self, action_request):
        action = action_request.get("action")
        if not action:
            # @NOTE: this potentionally should never happen but you never know
            self.log.error("No action supplied in current request")
            return False

        request = action_request.get("request")
        if not request:
            self.log.debug("No request body for action %s, sending action" %
                           action)
            self.obs_socket.send(action)
            # Success, breaking the loop
            return True

        template = TEMPLATES.get(request)
        if not template:
            self.log.error("Missing template for request %s" % request)
            # Keep searching
            return False

        target = action_request.get("target")
        if not target:
            self.log.error("Missing target in %s request for %s action" %
                           (request, action))
            # Keep searching
            return False

        self._action_buffer.append([self._action_counter, action, request])
        self.obs_socket.send(template % (self._action_counter, target))
        self._action_counter += 1

        # Explicit return is necessary here to avoid extra searching
        return True

    def start(self):
        self.log.info("Connecting to OBS...")
        self.obs_socket.run_forever()

    def close(self, teardown=False):
        self.log.debug("Attempting to close midi port")
        self.port.close()

        self.log.info("Midi connection has been closed successfully")

        # If close is requested during keyboard interrupt, let the websocket
        # client tear itself down and make a clean exit
        if not teardown:
            self.log.debug("Attempting to close OBS connection")
            self.obs_socket.close()

            self.log.info("OBS connection has been closed successfully")

        self.log.debug("Attempting to close TinyDB instance on config file")
        self.db.close()

        self.log.info("Config file has been successfully released")

    def __end__(self):
        self.log.info("Exiting script...")
        self.close()
Esempio n. 32
0
class wsPoloniex(Poloniex):
    # websocket stuff --------------------------------------------------
    def _on_message(self, ws, message):
        message = _loads(message)
        if 'error' in message:
            return logger.error(message['error'])

        if message[0] == 1002:
            if message[1] == 1:
                return logger.info('Subscribed to ticker')

            if message[1] == 0:
                return logger.info('Unsubscribed to ticker')

            data = message[2]
            data = [float(dat) for dat in data]
            self._tick[data[0]] = {
                'id': data[0],
                'last': data[1],
                'lowestAsk': data[2],
                'highestBid': data[3],
                'percentChange': data[4],
                'baseVolume': data[5],
                'quoteVolume': data[6],
                'isFrozen': data[7],
                'high24hr': data[8],
                'low24hr': data[9]
            }

    def _on_error(self, ws, error):
        logger.error(error)

    def _on_close(self, ws):
        if self._t._running:
            try:
                self.stop()
            except Exception as e:
                logger.exception(e)
            try:
                self.start()
            except Exception as e:
                logger.exception(e)
                self.stop()
        else:
            logger.info("Websocket closed!")

    def _on_open(self, ws):
        self._ws.send(_dumps({'command': 'subscribe', 'channel': 1002}))

    @property
    def tickerStatus(self):
        """
        Returns True if the websocket thread is running, False if not.
        """
        try:
            return self._t._running
        except:
            return False

    def startWebsocket(self):
        """ Run the websocket in a thread """
        self._tick = {}
        iniTick = self.returnTicker()
        self._ids = {market: iniTick[market]['id'] for market in iniTick}
        for market in iniTick:
            self._tick[self._ids[market]] = iniTick[market]

        self._ws = WebSocketApp("wss://api2.poloniex.com/",
                                on_open=self._on_open,
                                on_message=self._on_message,
                                on_error=self._on_error,
                                on_close=self._on_close)
        self._t = _Thread(target=self._ws.run_forever)
        self._t.daemon = True
        self._t._running = True
        self._t.start()
        logger.info('Websocket thread started')
        logger.debug(self._ws.url)

    def stopWebsocket(self):
        """ Stop/join the websocket thread """
        self._t._running = False
        self._ws.close()
        self._t.join()
        logger.info('Websocket thread stopped/joined')

    def marketTick(self, market=None):
        """ returns ticker from websocket if running/connected, else
        'self.returnTicker is used'"""
        if self.tickerStatus:
            if market:
                return self._tick[self._ids[market]]
            return self._tick
        logger.warning('Ticker is not running!')
        if market:
            return self.returnTicker()[market]
        return self.returnTicker()
Esempio n. 33
0
class WebsocketClient(Thread):
    _id = 0

    # type: int

    def __init__(
        self,
        session,
        # type: Streamlink
        url,
        # type: str
        subprotocols=None,
        # type: Optional[List[str]]
        header=None,
        # type: Optional[Union[List, Dict]]
        cookie=None,
        # type: Optional[str]
        sockopt=None,
        # type: Optional[Tuple]
        sslopt=None,
        # type: Optional[Dict]
        host=None,
        # type: Optional[str]
        origin=None,
        # type: Optional[str]
        suppress_origin=False,
        # type: bool
        ping_interval=0,
        # type: Union[int, float]
        ping_timeout=None,
        # type: Optional[Union[int, float]]
    ):
        if rootlogger.level <= TRACE:
            enableTrace(True, log)

        if not header:
            header = []
        if not any(True for h in header if h.startswith("User-Agent: ")):
            header.append("User-Agent: {0}".format(
                session.http.headers['User-Agent']))

        proxy_options = {}
        http_proxy = session.get_option("http-proxy")
        # type: Optional[str]
        if http_proxy:
            p = urlparse(http_proxy)
            proxy_options["proxy_type"] = p.scheme
            proxy_options["http_proxy_host"] = p.hostname
            if p.port:  # pragma: no branch
                proxy_options["http_proxy_port"] = p.port
            if p.username:  # pragma: no branch
                proxy_options["http_proxy_auth"] = unquote_plus(
                    p.username), unquote_plus(p.password or "")

        self._reconnect = False
        self._reconnect_lock = RLock()

        self.session = session
        self._ws_init(url, subprotocols, header, cookie)
        self._ws_rundata = dict(sockopt=sockopt,
                                sslopt=sslopt,
                                host=host,
                                origin=origin,
                                suppress_origin=suppress_origin,
                                ping_interval=ping_interval,
                                ping_timeout=ping_timeout,
                                **proxy_options)

        self._id += 1
        super(WebsocketClient, self).__init__(
            name="Thread-{0}-{1}".format(self.__class__.__name__, self._id))
        self.daemon = True

    def _ws_init(self, url, subprotocols, header, cookie):
        self.ws = WebSocketApp(url=url,
                               subprotocols=subprotocols,
                               header=header,
                               cookie=cookie,
                               on_open=self.on_open,
                               on_error=self.on_error,
                               on_close=self.on_close,
                               on_ping=self.on_ping,
                               on_pong=self.on_pong,
                               on_message=self.on_message,
                               on_cont_message=self.on_cont_message,
                               on_data=self.on_data)

    def run(self):
        # type: () -> None
        while True:
            log.debug("Connecting to: {0}".format(self.ws.url))
            self.ws.run_forever(**self._ws_rundata)
            # check if closed via a reconnect() call
            with self._reconnect_lock:
                if not self._reconnect:
                    return
                self._reconnect = False

    # ----

    def reconnect(self,
                  url=None,
                  subprotocols=None,
                  header=None,
                  cookie=None,
                  closeopts=None):
        # type: (str, Optional[List[str]], Optional[Union[List, Dict]], Optional[str], Optional[Dict]) -> None
        with self._reconnect_lock:
            # ws connection is not active (anymore)
            if not self.ws.keep_running:
                return
            log.debug("Reconnecting...")
            self._reconnect = True
            self.ws.close(**(closeopts or {}))
            self._ws_init(url=self.ws.url if url is None else url,
                          subprotocols=self.ws.subprotocols
                          if subprotocols is None else subprotocols,
                          header=self.ws.header if header is None else header,
                          cookie=self.ws.cookie if cookie is None else cookie)

    def close(self, status=STATUS_NORMAL, reason="", timeout=3):
        # type: (int, Union[str, bytes], int) -> None
        if is_py2:
            self.ws.close(status=status, reason=bytes(reason), timeout=timeout)
        else:
            self.ws.close(status=status,
                          reason=bytes(reason, encoding="utf-8"),
                          timeout=timeout)
        if self.is_alive():  # pragma: no branch
            self.join()

    def send(self, data, opcode=ABNF.OPCODE_TEXT):
        # type: (Union[str, bytes], int) -> None
        return self.ws.send(data, opcode)

    def send_json(self, data):
        # type: (Any) -> None
        return self.send(json.dumps(data, indent=None, separators=(",", ":")))

    # ----

    # noinspection PyMethodMayBeStatic
    def on_open(self, wsapp):
        # type: (WebSocketApp) -> None
        log.debug("Connected: {0}".format(wsapp.url))  # pragma: no cover

    # noinspection PyMethodMayBeStatic
    # noinspection PyUnusedLocal
    def on_error(self, wsapp, error):
        # type: (WebSocketApp, Exception) -> None
        log.error(error)  # pragma: no cover

    # noinspection PyMethodMayBeStatic
    # noinspection PyUnusedLocal
    def on_close(self, wsapp, status, message):
        # type: (WebSocketApp, int, str)
        log.debug("Closed: {0}".format(wsapp.url))  # pragma: no cover

    def on_ping(self, wsapp, data):
        # type: (WebSocketApp, str) -> None
        pass  # pragma: no cover

    def on_pong(self, wsapp, data):
        # type: (WebSocketApp, str) -> None
        pass  # pragma: no cover

    def on_message(self, wsapp, data):
        # type: (WebSocketApp, str) -> None
        pass  # pragma: no cover

    def on_cont_message(self, wsapp, data, cont):
        # type: (WebSocketApp, str, Any) -> None
        pass  # pragma: no cover

    def on_data(self, wsapp, data, data_type, cont):
        # type: (WebSocketApp, str, int, Any) -> None
        pass  # pragma: no cover
Esempio n. 34
0
class PoloniexSocketed(Poloniex):
    """ Child class of Poloniex with support for the websocket api """
    def __init__(self,
                 key=None,
                 secret=None,
                 subscribe={},
                 start=False,
                 *args,
                 **kwargs):
        super(PoloniexSocketed, self).__init__(key, secret, *args, **kwargs)
        self.socket = WebSocketApp(url="wss://api2.poloniex.com/",
                                   on_open=self.on_open,
                                   on_message=self.on_message,
                                   on_error=self.on_error,
                                   on_close=self.on_close)
        self._t = None
        self._running = False
        self.channels = {
            'account': {
                'id': '1000'
            },
            'ticker': {
                'id': '1002'
            },
            '24hvolume': {
                'id': '1003'
            },
            'heartbeat': {
                'id': '1010',
                'callback': self.on_heartbeat
            },
        }
        # add each market to channels list by id
        tick = self.returnTicker()
        for market in tick:
            self.channels[market] = {'id': str(tick[market]['id'])}
        # handle init subscribes
        if subscribe:
            self.setSubscribes(**subscribe)
        if start:
            self.startws()

    def _getChannelName(self, id):
        return next(
            (chan
             for chan in self.channels if self.channels[chan]['id'] == id),
            False)

    def on_open(self, *ws):
        for chan in self.channels:
            if 'sub' in self.channels[chan] and self.channels[chan]['sub']:
                self.subscribe(chan)

    def on_message(self, data):
        if not self.jsonNums:
            message = _loads(data, parse_float=str)
        else:
            message = _loads(data,
                             parse_float=self.jsonNums,
                             parse_int=self.jsonNums)
        # catch errors
        if 'error' in message:
            return self.logger.error(message['error'])
        chan = self._getChannelName(str(message[0]))
        # handle sub/unsub
        # skip heartbeats
        if not chan == 'heartbeat':
            # Subscribed
            if message[1] == 1:
                self.logger.debug('Subscribed to %s', chan)
                # return False so no callback trigger
                return False
            # Unsubscribed
            if message[1] == 0:
                self.logger.debug('Unsubscribed to %s', chan)
                # return False so no callback trigger
                return False
        if 'callback' in self.channels[chan]:
            # activate chan callback
            if chan == 'heartbeat':
                # show whole heartbeat
                self.socket._callback(self.channels[chan]['callback'], message)
            elif chan in ['ticker', '24hvolume']:
                # ticker and 24hvolume dont need seq id
                message = message[2:]
            else:
                # show seq id for everything else
                message = message[1:]
            self.socket._callback(self.channels[chan]['callback'], message)

    def on_error(self, error):
        self.logger.error(error)

    def on_close(self, *args):
        self.logger.info('Websocket Closed')

    def on_heartbeat(self, args):
        self.logger.debug(args)

    def setCallback(self, chan, callback):
        """ Sets the callback function for <chan> """
        if isinstance(chan, int):
            chan = self._getChannelName(str(chan))
        self.channels[chan]['callback'] = callback

    def setSubscribes(self, **subs):
        for sub in subs:
            if not sub in self.channels:
                self.logger.warning('Invalid channel: %s', sub)
            else:
                self.channels[sub]['sub'] = True
                self.channels[sub]['callback'] = subs[sub]

    def subscribe(self, chan, callback=None):
        """ Sends the 'subscribe' command for <chan> """
        if isinstance(chan, int):
            chan = self._getChannelName(str(chan))
        # account chan?
        if chan == 'account':
            # sending commands to 'account' requires a key, secret and nonce
            if not self.key or not self.secret:
                raise PoloniexError(
                    "self.key and self.secret needed for 'account' channel")
            self.channels[chan]['sub'] = True
            if callback:
                self.channels[chan]['callback'] = callback
            payload = {'nonce': self.nonce}
            payload_encoded = _urlencode(payload)
            sign = _new(self.secret.encode('utf-8'),
                        payload_encoded.encode('utf-8'), _sha512)

            self.socket.send(
                _dumps({
                    'command': 'subscribe',
                    'channel': self.channels[chan]['id'],
                    'sign': sign.hexdigest(),
                    'key': self.key,
                    'payload': payload_encoded
                }))
        else:
            self.channels[chan]['sub'] = True
            if callback:
                self.channels[chan]['callback'] = callback
            self.socket.send(
                _dumps({
                    'command': 'subscribe',
                    'channel': self.channels[chan]['id']
                }))

    def unsubscribe(self, chan):
        """ Sends the 'unsubscribe' command for <chan> """
        if isinstance(chan, int):
            chan = self._getChannelName(str(chan))
        # account chan?
        if chan == 'account':
            # sending commands to 'account' requires a key, secret and nonce
            if not self.key or not self.secret:
                raise PoloniexError(
                    "self.key and self.secret needed for 'account' channel")
            self.channels[chan]['sub'] = False
            payload = {'nonce': self.nonce}
            payload_encoded = _urlencode(payload)
            sign = _new(self.secret.encode('utf-8'),
                        payload_encoded.encode('utf-8'), _sha512)

            self.socket.send(
                _dumps({
                    'command': 'unsubscribe',
                    'channel': self.channels[chan]['id'],
                    'sign': sign.hexdigest(),
                    'key': self.key,
                    'payload': payload_encoded
                }))
        else:
            self.channels[chan]['sub'] = False
            self.socket.send(
                _dumps({
                    'command': 'unsubscribe',
                    'channel': self.channels[chan]['id']
                }))

    def startws(self, subscribe=False):
        """
        Run the websocket in a thread, use 'subscribe' arg to subscribe
        to channels on start
        """
        self._t = Thread(target=self.socket.run_forever)
        self._t.daemon = True
        self._running = True
        # set subscribes
        if subscribe:
            self.setSubscribes(**subscribe)
        self._t.start()
        self.logger.info('Websocket thread started')

    def stopws(self, wait=1):
        """ Stop/join the websocket thread """
        self._running = False
        # unsubscribe from subs
        for chan in self.channels:
            if 'sub' in self.channels[chan] and self.channels[chan]['sub']:
                self.unsubscribe(chan)
        sleep(wait)
        try:
            self.socket.close()
        except Exception as e:
            self.logger.exception(e)
        self._t.join()
        self.logger.info('Websocket thread stopped/joined')