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]
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
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()
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()
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()
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()
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()
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!")
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()
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('我自己正常結束的')
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()
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
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)
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)
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')
def close(self): WebSocketApp.close(self) self.__client_state = CMDClient.State.DISCONNECTED
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
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}')
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
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))
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
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())
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)
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()
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 }
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()
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)
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())
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()
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()
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
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')