def msg_handler(ws: websocket.WebSocketApp, message: AnyStr): message = json.loads(message) print(f'student {i} received message: {message["command"]}') time.sleep(random.randint(0, 5)) if 'command' in message and message['command'] == 'intro': time.sleep(random.randint(0, 5)) ws.send(json.dumps({'command': 'next'})) print(f'student {i} hit next') if 'command' in message and message['command'] == 'in_progress': time.sleep(random.randint(0, 5)) if not all_answered(message['result']['questions']): print(f'student {i} answering questions') answer_questions(i, ws, message['result']['questions']) else: print(f'student {i} done answering questions') ws.send(json.dumps({'command': 'next'})) print(f'student {i} hit next') if 'command' in message and message['command'] == 'complete': print(f'student {i} completed a text reading and quit.') print(f'student {i} results: {message["result"]}') ws.keep_running = False if 'command' in message and message['command'] == 'exception': print(f'student {i} got an exception: {message}') ws.keep_running = False
def _send_ack(ws: WebSocketApp, msg): envelope_id = msg.get('envelope_id') if envelope_id: # Send ACK ws.send(json.dumps({ 'envelope_id': envelope_id, }))
class Publisher(): def __init__(self): self._endpoint = None self._wsserver = None self._wsclient = None def bind(self, endpoint): self._endpoint = endpoint class WampApplication(WebSocketApplication): protocol_class = WampProtocol def on_open(self): wamp = self.protocol wamp.register_pubsub('http://topic1') ep = urlparse(self._endpoint) resource = Resource({'^%s$' % ep.path: WampApplication}) self._wsserver = WebSocketServer(ep.netloc, resource, debug=True) gevent.spawn(self._wsserver.serve_forever) self._wsclient = WebSocketClient(self._endpoint) gevent.spawn(self._wsclient.run_forever) def publish(self, topic, message): data = json.dumps([WampProtocol.MSG_PUBLISH, topic, message]) print data self._wsclient.send(data)
class GameSocket: def __init__(self, cookie): self.game_socket = None self.cookie = cookie pass @staticmethod def on_open(ws): def run(*args): while (True): pass thread.start_new_thread(run, ()) def open_game(self): def on_recive(ws, message): message = json.loads(message) self.handle(message) pass self.game_socket = WebSocketApp("ws://localhost:8080/game", cookie=self.cookie, on_message=on_recive) self.game_socket.on_open = self.on_open self.game_socket.run_forever() def handle(self, message): print(message) tp = message["type"] if tp == "GetCardFromHand": self.game_socket.send( '{"type":"ChooseCardFromHand", "chosenCard":0}') if tp == "GetCardFromTable": self.game_socket.send( '{"type":"ChooseCardFromTable", "chosenCard":0}')
def _ws_on_open(self, ws: websocket.WebSocketApp): """Callback for sending the initial authentication data This "payload" contains the required data to authenticate this websocket client as a suitable bot connection to the Discord websocket. Args: ws: websocket connection """ payload = { 'op': WebSocketEvent.IDENTIFY.value, 'd': { 'token': self.token, 'properties': { '$os': sys.platform, '$browser': 'Pycord', '$device': 'Pycord', '$referrer': '', '$referring_domain': '' }, 'compress': True, 'large_threshold': 250 } } self.logger.debug('Sending identify payload') ws.send(json.dumps(payload)) self.connected = True
def send(self, cmd): if isinstance(cmd, str): cmd = json.loads(cmd) if self.__client_state is not CMDClient.State.CONNECTED: raise SendCommandError("Client is not connected to command server") if not self.__validate_cmd(cmd): raise SendCommandError("Illegal command") cmd['type'] = str(cmd['type']) WebSocketApp.send(self, json.dumps(cmd))
def answer_question(i: int, ws: websocket.WebSocketApp, question: Dict): time.sleep(random.randint(0, 1)) answers = question['answers'] rand_ans = question['answers'][random.randint(0, len(answers) - 1)] ws.send(json.dumps({'command': 'answer', 'answer_id': rand_ans['id']})) print( f'student {i} answered question {question["id"]} with answer {rand_ans["id"]}' )
def _stream_data(buffer, bufferSize: int, wsConn: WebSocketApp, event: Event) -> bool: while True: data = buffer.read(bufferSize) if not data: break elif event.is_set(): event.clear() return False wsConn.send(data, ABNF.OPCODE_BINARY) return True
def on_open(self, ws: WebSocketApp): self.logger.info('WebSocket connected') ws.send(f'PASS {self.oauth_token}') ws.send(f'NICK {self.nick}') ws.send(f'JOIN #{self.channel}') ws.send(f'CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership') self._callback(self.post_login)
class WebSocketClient(AbstractWebSocketClient): __instance: Optional['WebSocketClient'] = None def __init__(self, logger: AbstractLogger = Logger()): if WebSocketClient.__instance: raise Exception('Use get_instance() instead!') self.__ws = WebSocketApp( 'wss://jqbx.fm/socket.io/?EIO=3&transport=websocket') self.__logger = logger WebSocketClient.__instance = self @staticmethod def get_instance() -> 'WebSocketClient': if WebSocketClient.__instance is None: WebSocketClient() return WebSocketClient.__instance def register(self, on_open: Callable[[], None], on_message: Callable[[WebSocketMessage], None], on_error: Callable[[Any], None], on_close: Callable[[], None]) -> None: self.__ws.on_open = lambda _: on_open() self.__ws.on_message = lambda _, raw_message: on_message( self.__parse(raw_message)) def run(self) -> None: self.__ws.run_forever() def send(self, web_socket_message: WebSocketMessage) -> None: self.__logger.info('Outgoing Message', web_socket_message.as_dict()) serialized = str(web_socket_message.code) array_part = [ x for x in [web_socket_message.label, web_socket_message.payload] if x ] if array_part: serialized += json.dumps(array_part) self.__ws.send(serialized) @staticmethod def __parse(raw_message: str) -> WebSocketMessage: stripped = raw_message.strip() parts = stripped.split('[', 1) label = None payload = None if len(parts) > 1: json_array = json.loads('[%s' % parts[1]) label = json_array[0] payload = None if len(json_array) == 1 else json_array[1] return WebSocketMessage(int(parts[0]), label, payload)
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 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 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 WsClient(object): def __init__(self, url: str, key: str): """ :param url: :param key: """ self._url = url self._key = key self._ws_client = WebSocketApp(self._url) def get_user_info(self): return self._ws_client.send(self._key) def send(self): """ :return: """ return self._ws_client.send(self._key)
class chatUser(): messageList = None def __init__(self, info, eventList, sleepTime, waitTime, id): self.actionList = eventList self.sleepTime = sleepTime self.id = id self.messageList = [] time.sleep(waitTime) print('\nweb socket-', id, ' setting: ', info, ' start_at: ', int(time.time() * 1000)) self.ws = WebSocketApp(info, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close) self.ws.on_open = self.on_open self.ws.run_forever() def __del__(self): print('call del fun') self.messageList = None self.actionList = None def on_message(self, message): data = json.loads(message) pprint(data) self.messageList.insert(0, data) def on_error(self, error): print('get ', self.id, ' error: ', error) def on_close(self): print("webSocket-", self.id, " has been closed at ", int(time.time())) def on_open(self): for i in self.actionList: pprint(i) waitTime = i.pop('sleep') time.sleep(waitTime) self.ws.send(json.dumps(i)) time.sleep(self.sleepTime)
class GameSocket: def __init__(self, cookie, testsystem): self.game_socket = None self.cookie = cookie self.testsystem = testsystem self.errorst = False self.error_next = False self.good_msg = '' pass @staticmethod def on_open(ws): def run(*args): while(True): pass thread.start_new_thread(run, ()) def open_game(self): def on_recive(ws,message): message = json.loads(message) self.handle(message) pass self.game_socket = WebSocketApp("ws://localhost:8080/game",cookie=self.cookie, on_message=on_recive) self.game_socket.on_open = self.on_open self.game_socket.run_forever() def truehandl(self,message): tp = message["type"] if tp == "ErrorMsg": #print("error handled") self.game_socket.send(self.good_msg) self.error_next = False self.errorst = False if self.error_next: self.testsystem.fail("error was not") def fakehandl(self,message): tp = message["type"] if tp == "GetCardFromHand": self.game_socket.send('#####ERROR#####') self.good_msg = '{"type":"ChooseCardFromHand", "chosenCard":0}' #print("error send") self.errorst = True self.error_next = True if tp == "GetCardFromTable": self.game_socket.send('#####ERROR#####') self.good_msg = '{"type":"ChooseCardFromTable", "chosenCard":0}' #print("error send") self.errorst = True self.error_next = True def handle(self,message): #print(message) #print(self.errorst) if self.errorst: self.truehandl(message) else: self.fakehandl(message)
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 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 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 SkyroomBot: def __init__(self, url: str, username: str, password: str, token: str, on_message: Callable[[str], None] = None): self.is_opened = False self.username = username self.password = password self.token = token self.url = url self.gather_data_from_url() self.logger = logging.getLogger(__class__.__name__) if on_message == None: on_message = self.on_message self.ws = WebSocketApp(self.websocket_addr, \ on_open = lambda ws: self.on_open(), \ on_message = lambda ws, msg: on_message(msg),\ on_close = lambda ws: self.on_close() ) def gather_data_from_url(self): if self.url[-1] == '/': self.url[: len(self.url) - 1] url_split = self.url.split("/") room = url_split[-1] customer = url_split[-2] data = '------WebKitFormBoundaryLYlCWZpm2cRdPd4B\r\nContent-Disposition: form-data; name="customer"\r\n\r\n%s\r\n------WebKitFormBoundaryLYlCWZpm2cRdPd4B\r\nContent-Disposition: form-data; name="room"\r\n\r\n%s\r\n------WebKitFormBoundaryLYlCWZpm2cRdPd4B\r\nContent-Disposition: form-data; name="gadget"\r\n\r\nSkyroom\r\n------WebKitFormBoundaryLYlCWZpm2cRdPd4B\r\nContent-Disposition: form-data; name="action"\r\n\r\nFetchRoom\r\n------WebKitFormBoundaryLYlCWZpm2cRdPd4B--\r\n' \ % (customer, room) headers = { "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36", "content-type": "multipart/form-data; boundary=----WebKitFormBoundaryLYlCWZpm2cRdPd4B", } res = post("%s?%d" % (self.url, time()*1000), data=data, headers=headers) if res.status_code != 200: raise Exception("Invalid skyroom url") res = loads(res.content)["result"] self.customer_id = res["customer_id"] self.room_id = res["room_id"] self.websocket_addr = str(res["server"]).replace("https", "wss") def on_open(self): self.is_opened = True self.join_room() Thread(target=self.keep_alive).start() def on_message(self, msg: str): self.logger.info(msg) def send_chat(self, msg): msg_info = { "id": int(time()), "text": msg } self.ws.send(dumps(["s", "chat", "message-new", msg_info])) def on_close(self): self.is_opened = False def join_room(self): payload = [ "s", "user", "join", { "origin": "www.skyroom.online", "app_id": "27642275", "room_id": self.room_id, "customer_id": self.customer_id, "username": self.username, "password": self.password, "token": self.token, "nickname": "", "platform": { "version":"12.4.8", "os": 1, "browser": 0 } } ] self.ws.send(dumps(payload)) self.logger.info("Joined room") def keep_alive(self): alive_expire = time() + 5 while self.is_opened: if alive_expire < time(): self.ws.send("imalive") alive_expire = time() + 5 self.logger.info("Connection closed!")
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 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 Subscriber(): def __init__(self): self._endpoint = None # we actually just use the constant definitions self._wamp = WampProtocol(None) self._ws = None self._session_id = None self._handlers_table = {'': set()} self._received = list() def connect(self, endpoint): self._endpoint = endpoint self._ws = WebSocketClient(self._endpoint, on_message=self._on_message) gevent.spawn(self._ws.run_forever, None, None, 10) def subscribe(self, topic, handler): #self._ws.send(json.dumps([self._wamp.MSG_PREFIX, topic.split(':')[0], topic])) self._ws.send(json.dumps([self._wamp.MSG_SUBSCRIBE, topic])) # TODO wait for SUBSCRIBED if handler: self._add_handler(topic, handler) def unsubscribe(self, topic): self._ws.send([self._wamp.MSG_UNSUBSCRIBE, topic]) # TODO wait for UNSUBSCRIBED return self._handlers_table.pop(topic, set()) def receive(self): return self._received.pop(0) def run(self): self._ws = WebSocketClient(self._endpoint, on_message=self._on_message) gevent.spawn(self._receiver) gevent.spawn(self._ws.run_forever, None, None, 10) def _receiver(self): while True: topic, message = self.receive() handlers = self._handlers_table.get(topic, set()) for handler in handlers: handler(topic, message) def _on_message(self, sock, msg): data = json.loads(msg) if data[0] == self._wamp.MSG_WELCOME and len(data) > 1: self._session_id = data[1] elif data[0] == self._wamp.MSG_EVENT and len(data) >= 3: topic, message = data[1], data[2] # TODO list length limit self.received.append((topic, message)) else: raise Exception('Unexpected message received') def _add_handler(self, topic, handler): def _add_topic_handler(topic, handler): handlers = self._handlers_table.get(topic, set()) handlers |= set([handler]) self._handlers_table[topic] = handlers if callable(handler): if topic == '': for tpc in self._handlers_table.keys(): _add_topic_handler(tpc, handler) else: _add_topic_handler(topic, handler) else: raise ValueError('invalid handler')
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 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 Slack(Base): """Provide bot for Slack.""" def __init__(self, token: str = '', plugins: Iterable[PluginConfig] = None, max_workers: int = None) -> None: """Initializer. :param token: Access token provided by Slack. :param plugins: List of plugin modules. :param max_workers: Optional number of worker threads. :return: None """ super().__init__(plugins=plugins, max_workers=max_workers) self.client = self.setup_client(token=token) self.message_id = 0 self.ws = None # type: WebSocketApp self.connect_attempt_count = 0 def setup_client(self, token: str) -> SlackClient: """Setup ClackClient and return its instance. :param token: Slack access token. :return: SlackClient instance """ return SlackClient(token=token) def connect(self) -> None: """Connect to Slack websocket server and start interaction. :return: None """ while self.connect_attempt_count < 10: try: self.connect_attempt_count += 1 self.try_connect() except KeyboardInterrupt: break except Exception as e: logging.error(e) time.sleep(self.connect_attempt_count) else: logging.error("Attempted 10 times, but all failed. Quitting.") def try_connect(self) -> None: """Try establish connection with Slack websocket server.""" try: response = self.client.get('rtm.start') if 'url' not in response: raise Exception("url is not in the response. %s" % response) except Exception as e: raise SarahSlackException( "Slack request error on /rtm.start. %s" % e) else: 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 generate_schedule_job(self, command: ScheduledCommand) \ -> Optional[Callable[..., None]]: """Generate callback function to be registered to scheduler. This creates a function that execute given command and then handle the command response. If the response is SlackMessage instance, it make HTTP POST request to Slack web API endpoint. If string is returned, then it submit it to the message sending worker. :param command: ScheduledCommand object that holds job information :return: Optional callable object to be scheduled """ channels = command.schedule_config.pop('channels', []) if not channels: logging.warning( 'Missing channels configuration for schedule job. %s. ' 'Skipping.' % command.module_name) return None def job_function() -> None: ret = command() if isinstance(ret, SlackMessage): for channel in channels: # TODO Error handling data = {'channel': channel} data.update(ret.to_request_params()) self.client.post('chat.postMessage', data=data) else: for channel in channels: self.enqueue_sending_message(self.send_message, channel, str(ret)) return job_function @concurrent def message(self, _: WebSocketApp, event: str) -> None: """Receive event from Slack and dispatch it to corresponding method. :param _: WebSocketApp instance. This is not to be used here. :param event: JSON string that contains event information. :return: 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: %s. error: %s' % ( decoded_event['reply_to'], decoded_event.get('error', ""))) return None # 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"}, 'presence_change': { 'description': "A team member's presence changed"}, 'team_migration_started': { 'method': self.handle_team_migration, 'description': "The team is being migrated between servers"} } # type: EventTypeMap 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 None if decoded_event['type'] not in type_map: logging.error('Unknown type value is given. %s' % event) return None logging.debug( '%s: %s. %s' % ( decoded_event['type'], type_map[decoded_event['type']].get('description', 'NO DESCRIPTION'), event)) method = type_map[decoded_event['type']].get('method', None) if method: method(decoded_event) return None def handle_hello(self, _: Dict) -> None: """Handle hello event. This is called when connection is established. :param _: Dictionary that represent event. This is not used here. :return: None """ self.connect_attempt_count = 0 # Reset retry count logging.info('Successfully connected to the server.') def handle_message(self, content: Dict) -> Optional[Future]: """Handle message event. :param content: Dictionary that represent event. :return: Optional Future instance that represent message sending result. """ # 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 None ret = self.respond(content['user'], content['text']) if isinstance(ret, SlackMessage): # TODO Error handling data = {'channel': content["channel"]} data.update(ret.to_request_params()) self.client.post('chat.postMessage', data=data) return None elif isinstance(ret, str): return self.enqueue_sending_message(self.send_message, content['channel'], ret) def handle_team_migration(self, _: Dict) -> None: """Handle team_migration_started event. https://api.slack.com/events/team_migration_started "When clients recieve this event they can immediately start a reconnection process by calling rtm.start again." :param _: Dictionary that represent event. :return: None """ logging.info("Team migration started.") def on_error(self, _: WebSocketApp, error: Exception) -> None: """Callback method called by WebSocketApp when error occurred. :param _: WebSocketApp instance that is not currently used. :param error: Exception instance :return: None """ logging.error("error %s", error) def on_open(self, _: WebSocketApp) -> None: """Callback method called by WebSocketApp on connection establishment. :param _: WebSocketApp instance that is not currently used. :return: None """ logging.info('connected') def on_close(self, _: WebSocketApp, code: int, reason: str) -> None: """Callback method called by WebSocketApp when connection is closed. :param _: WebSocketApp instance that is not currently used. :param code: Closing code described in RFC6455. https://tools.ietf.org/html/rfc6455#section-7.4 :param reason: Closing reason. :return: None """ logging.info('connection closed. code: %d. reason: %s', code, reason) def send_message(self, channel: str, text: str, message_type: str = 'message') -> None: """Send message to Slack via websocket connection. :param channel: Target channel to send message. :param text: Sending text. :param message_type: Message type. Default is "message." :return: 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: """Return unique ID for sending message. 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. :return: Unique ID as int """ self.message_id += 1 return self.message_id
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 TracerApp(object): """ Main tracer application. """ def __init__(self, options): self.counter = 0 self.host = options.host self.port = options.port self.ios = options.ios self.page_num = options.page_num self.timeStart = time.mktime(time.localtime()) self.shouldStep = True # If we should try to single step the debugger self._isStepping = False # If we are currently inside stepping self.ws_url = None self.ws = None self._requestDetails = {} # Map of details about a request def showConnectionList(self): """ Print out list of possible debugger connections """ # Mobile safari has a different listing page if self.ios: pages_url = "http://%s:%s/listing.json" % (self.host, self.port) else: pages_url = "http://%s:%s/json" % (self.host, self.port) pages = urllib2.urlopen(pages_url) pages_data = json.loads(pages.read()) for page in pages_data: print "----------------------------------------------------------" if self.ios: print "Page: ", page.get('pageTitle') print " id: ", page.get('pageId') print " url: ", page.get('pageURL') print " debug url: ", page.get('debugURL') else: print "Page: ", page.get('title', '') print " url: ", page.get('url', '') print " ws_debug_url: ", page.get('webSocketDebuggerUrl', '') def start(self): self.ws_url = "ws://%s:%s/devtools/page/%s" % (self.host, self.port, self.page_num) self.ws = WebSocketApp(self.ws_url, on_open = self.onOpen, on_message = self.onMessage, on_error = self.onError, on_close = self.onClose, ipv6 = self.ios) self.ws.run_forever() def send(self, method, params = None): self.counter += 1 # separators is important, you'll get "Message should be in JSON format." otherwise msg_data = {"id": self.counter, "method": method} if params is not None: msg_data['params'] = params message = json.dumps(msg_data, separators=(',', ':')) print "> %s" % (message,) self.ws.send(message) def recv(self): result = self.ws.recv() print "< %s" % (result,) return result # ---- PRIMARY CALLBACKS ---- # def onOpen(self, ws): #self.send('Runtime.evaluate', {'expression': '1+1'}) #self.send('Runtime.evaluate', {'expression': 'alert("hello from python")'}) #self.send('Timeline.start', {'maxCallStackDepth': 5}) #self.send('Network.enable') #self.send('Console.enable') #self.send('Timeline.start', {'maxCallStackDepth': 5}) self.send('Debugger.enable') def onMessage(self, ws, message): # Decode message into a bunch object to make easier to access attributes # but store original message so we can get to it msg = Bunch.loads(message) msg._rawMsg = json.loads(message) if hasattr(msg, 'result'): self.handleResultMsg(msg) elif hasattr(msg, 'method'): self.handleMethodMsg(msg) else: print "UNKNOWN MSG TYPE: " self.prettyPrintMsg(msg) def onError(self, ws, error): print "error: ", error def onClose(self, ws): print "CLOSED" # --- Helpers ---- # def prettyPrintMsg(self, msg): if type(msg) in types.StringTypes: msg = json.loads(msg) elif type(msg) == Bunch: msg = msg._rawMsg print json.dumps(msg, sort_keys=True, indent=3) # --- MSG Helpers --- # def handleResultMsg(self, msg): print "RESULT: [%s]" % msg.id print json.dumps(msg._rawMsg['result'], sort_keys=True, indent=3) def handleMethodMsg(self, msg): """ Try to map method name to a local handler. get name as: 'handle' + method in camel case and no .'s """ def replace(match): return match.group()[1].upper() method = msg.method handler_name = 'handle' + re.sub('\.\w', replace, method) handler_method = getattr(self, handler_name, self.handleNotificationDefault) handler_method(msg) def handleNotificationDefault(self, msg): """ By default just print the method name. """ print self.getMethodHeader(msg) # --- Console Notification Processing --- # def handleConsoleMessageAdded(self, msg): print self.getMethodHeader(msg) cmsg = msg.params.message print " msg: [%s] %s" % (cmsg.level, cmsg.text) # --- Timeline Notification Processing -- # def handleTimelineEventRecorded(self, msg): record = msg.params.record record_raw = msg._rawMsg.get('params').get('record') print "[%s] ==[ %s:%s ]=====" % ('', #self.getDeltaTsStr(record.endTime), msg.method, record.type) print " mem: %.2f/%.2f MB" % (record.usedHeapSize / (1024.0*1024.0), record.totalHeapSize / (1024.0*1024.0)) if record.has_key('endTime'): print " duration: ", (record.endTime - record.startTime) print " data: ", json.dumps(record_raw.get('data'), indent = 2) #print " children: ", json.dumps(record_raw.get('children'), indent = 2) # --- Network Notification Processing --- # def handleNetworkDataReceived(self, msg): print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) print " dataLen: ", msg.params.dataLength def handleNetworkLoadingFailed(self, msg): print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) print " error: ", msg.params.errorText def handleNetworkLoadingFinished(self, msg): print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) def handleNetworkRequestServedFromCache(self, msg): print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) def handleNetworkRequestServedFromMemoryCache(self, msg): print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) print " url: ", msg.params.documentURL def handleNetworkRequestWillBeSent(self, msg): request_id = msg.params.get_first('requestId', 'identifier') self._requestDetails[request_id] = Bunch(requestId = request_id, request = msg.params.request, loaderId = msg.params.loaderId, documentUrl = msg.params.documentURL, startTs = msg.params.timestamp, initiator = msg.params.get('initiator', None), stack = msg.params.stackTrace) print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) print " url: ", msg.params.documentURL def handleNetworkResponseReceived(self, msg): resp = msg.params.response print self.getMethodHeader(msg) print " request: ", self.getRequestSummary(msg) print " type: ", msg.params.type print " reused: ", resp.connectionReused print " from disk: ", resp.fromDiskCache print " mime: ", resp.mimeType print " status: [%s] %s" % (resp.status, resp.statusText) def handleDebuggerDebuggerWasEnabled(self, msg): print self.getMethodHeader(msg) if self.shouldStep: self.send('Debugger.pause') # self.send('Debugger.stepInto') # self._isStepping = True def handleDebuggerPaused(self, msg): print self.getMethodHeader(msg) print " reason: ", msg.params.reason params_raw = msg._rawMsg.get('params') print " data: ", json.dumps(params_raw.get('data'), indent = 2) print " frame: ", json.dumps(params_raw.get('callFrames'), indent = 2) if self.shouldStep: self.send('Debugger.stepInto') def handleDebuggerResumed(self, msg): print self.getMethodHeader(msg) def handleDebuggerScriptParsed(self, msg): print self.getMethodHeader(msg) print json.dumps(msg._rawMsg.get('params'), indent = 2) # ---- Helpers ---- # def getMethodHeader(self, msg): return "[%s] ==[ %s ]=====" % (self.getTS(msg), msg.method) def getRequestSummary(self, msg): request_id = msg.params.get_first('requestId', 'identifier') req_record = self._requestDetails.get(request_id, None) if req_record: return "[%s] %s" % (request_id, req_record.request.url) else: return "[%s] {unknown}" % request_id def getTS(self, msg): """ Returns a timestamp string to use as prefix """ ts_value = None if hasattr(msg, 'params'): ts_value = msg.params.get('timestamp', None) if ts_value is None: return '<nots>' else: return self.getDeltaTsStr(ts_value) def getDeltaTsStr(self, ts): ts_delta = ts - self.timeStart return "%8.4f" % ts_delta
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 DXJobLogStreamClient: def __init__( self, job_id, input_params=None, msg_output_format="{job} {level} {msg}", msg_callback=None, print_job_info=True ): self.job_id = job_id self.input_params = input_params self.msg_output_format = msg_output_format self.msg_callback = msg_callback self.print_job_info = print_job_info self.seen_jobs = {} self.error = False self.exception = None self.closed_code = None self.closed_reason = None self.url = "{protocol}://{host}:{port}/{job_id}/getLog/websocket".format( protocol='wss' if dxpy.APISERVER_PROTOCOL == 'https' else 'ws', host=dxpy.APISERVER_HOST, port=dxpy.APISERVER_PORT, job_id=job_id ) self._app = None def connect(self): while True: self.error = False self.exception = None self.closed_code = None self.closed_reason = None try: self._app = WebSocketApp( self.url, on_open=self.opened, on_close=self.closed, on_error=self.errored, on_message=self.received_message ) self._app.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) except: if not self.server_restarted(): raise finally: self._app = None if self.server_restarted(): # Instead of trying to reconnect in a retry loop with backoff, run an # API call that will do the same and block while it retries. logger.warn("Server restart, reconnecting...") time.sleep(1) dxpy.describe(self.job_id) else: break def server_restarted(self): return ( self.closed_code == 1001 and self.closed_reason == "Server restart, please reconnect later" ) def opened(self): args = { "access_token": dxpy.SECURITY_CONTEXT['auth_token'], "token_type": dxpy.SECURITY_CONTEXT['auth_token_type'] } if self.input_params: args.update(self.input_params) self._app.send(json.dumps(args)) def errored(self, exception=None): self.error = True self.exception = exception def closed(self, code=None, reason=None): if code: self.closed_code = code self.closed_reason = reason elif not self.error: self.closed_code = 1000 self.closed_reason = "Normal" elif self.exception and type(self.exception) in {KeyboardInterrupt, SystemExit}: self.closed_code = 1000 self.closed_reason = "Connection terminated by client" else: self.closed_code = 1006 self.closed_reason = str(self.exception) if self.exception else "Abnormal" if self.closed_code != 1000: try: error = json.loads(self.closed_reason) raise DXJobLogStreamingException( "Error while streaming job logs: {type}: {message}\n".format( **error ) ) except (KeyError, ValueError): raise DXJobLogStreamingException( "Error while streaming job logs: {code}: {reason}\n".format( code=self.closed_code, reason=self.closed_reason ) ) elif self.print_job_info: if self.job_id not in self.seen_jobs: self.seen_jobs[self.job_id] = {} for job_id in self.seen_jobs.keys(): self.seen_jobs[job_id] = dxpy.describe(job_id) print( get_find_executions_string( self.seen_jobs[job_id], has_children=False, show_outputs=True ) ) else: self.seen_jobs[self.job_id] = dxpy.describe(self.job_id) if self.seen_jobs[self.job_id].get('state') in {'failed', 'terminated'}: err_exit(code=3) def received_message(self, message): message_dict = json.loads(message) if ( self.print_job_info and 'job' in message_dict and message_dict['job'] not in self.seen_jobs ): self.seen_jobs[message_dict['job']] = dxpy.describe(message_dict['job']) print( get_find_executions_string( self.seen_jobs[message_dict['job']], has_children=False, show_outputs=False ) ) if ( message_dict.get('source') == 'SYSTEM' and message_dict.get('msg') == 'END_LOG' ): self._app.keep_running = False elif self.msg_callback: self.msg_callback(message_dict) else: print(self.msg_output_format.format(**message_dict))
class DXJobLogStreamClient: def __init__( self, job_id, input_params=None, msg_output_format="{job} {level} {msg}", msg_callback=None, print_job_info=True, exit_on_failed=True ): """Initialize job log client. :param job_id: dxid for a job (hash ID 'job-xxxx') :type job_id: str :param input_params: blob with connection parameters, should have keys ``numRecentMessages`` (int) (wich may not be more than 1024 * 256, otherwise no logs will be returned), ``recurseJobs`` (bool) - if True, attempts to traverse subtree ``tail`` (bool) - if True, keep watching job. Should also be set to True to get all logs from a completed job. :type input_params: dict :param msg_output_format: how messages should be printed to console. Ignored if ``msg_callback`` is specified :type msg_output_form: str :param print_job_info: if True, prints metadata about job :type print_job_info: bool :param msg_callback: single argument function that accepts a JSON blob with message details. Example: ``{"timestamp": 1575465039481, "source": "APP", "level": "STDOUT", "job": "job-123", "line":24, "msg": "success WfFragment"}`` where ``timestamp`` is Unix epoch time in milliseconds and ``line`` is a sequence number. :type msg_callback: callable :param exit_on_failed: if True, will raise SystemExit with code of 3 if encountering a failed job (this is the default behavior) :type exit_on_failed: bool """ # TODO: add unit tests; note it is a public class self.job_id = job_id self.input_params = input_params self.msg_output_format = msg_output_format self.msg_callback = msg_callback self.print_job_info = print_job_info self.seen_jobs = {} self.error = False self.exception = None self.closed_code = None self.closed_reason = None self.exit_on_failed = exit_on_failed self.url = "{protocol}://{host}:{port}/{job_id}/getLog/websocket".format( protocol='wss' if dxpy.APISERVER_PROTOCOL == 'https' else 'ws', host=dxpy.APISERVER_HOST, port=dxpy.APISERVER_PORT, job_id=job_id ) self._app = None def connect(self): while True: self.error = False self.exception = None self.closed_code = None self.closed_reason = None try: self._app = WebSocketApp( self.url, on_open=self.opened, on_close=self.closed, on_error=self.errored, on_message=self.received_message ) self._app.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) except: if not self.server_restarted(): raise finally: self._app = None if self.server_restarted(): # Instead of trying to reconnect in a retry loop with backoff, run an # API call that will do the same and block while it retries. logger.warn("Server restart, reconnecting...") time.sleep(1) dxpy.describe(self.job_id) else: break def server_restarted(self): return ( self.closed_code == 1001 and self.closed_reason == "Server restart, please reconnect later" ) def opened(self): args = { "access_token": dxpy.SECURITY_CONTEXT['auth_token'], "token_type": dxpy.SECURITY_CONTEXT['auth_token_type'] } if self.input_params: args.update(self.input_params) self._app.send(json.dumps(args)) def errored(self, exception=None): self.error = True self.exception = exception def closed(self, code=None, reason=None): if code: self.closed_code = code self.closed_reason = reason elif not self.error: self.closed_code = 1000 self.closed_reason = "Normal" elif self.exception and type(self.exception) in {KeyboardInterrupt, SystemExit}: self.closed_code = 1000 self.closed_reason = "Connection terminated by client" else: self.closed_code = 1006 self.closed_reason = str(self.exception) if self.exception else "Abnormal" if self.closed_code != 1000: try: error = json.loads(self.closed_reason) raise DXJobLogStreamingException( "Error while streaming job logs: {type}: {message}\n".format( **error ) ) except (KeyError, ValueError): raise DXJobLogStreamingException( "Error while streaming job logs: {code}: {reason}\n".format( code=self.closed_code, reason=self.closed_reason ) ) elif self.print_job_info: if self.job_id not in self.seen_jobs: self.seen_jobs[self.job_id] = {} for job_id in self.seen_jobs.keys(): self.seen_jobs[job_id] = dxpy.describe(job_id) print( get_find_executions_string( self.seen_jobs[job_id], has_children=False, show_outputs=True ) ) else: self.seen_jobs[self.job_id] = dxpy.describe(self.job_id) if (self.exit_on_failed and self.seen_jobs[self.job_id].get('state') in {'failed', 'terminated'}): err_exit(code=3) def received_message(self, message): message_dict = json.loads(message) if ( self.print_job_info and 'job' in message_dict and message_dict['job'] not in self.seen_jobs ): self.seen_jobs[message_dict['job']] = dxpy.describe(message_dict['job']) print( get_find_executions_string( self.seen_jobs[message_dict['job']], has_children=False, show_outputs=False ) ) if ( message_dict.get('source') == 'SYSTEM' and message_dict.get('msg') == 'END_LOG' ): self._app.keep_running = False elif self.msg_callback: self.msg_callback(message_dict) else: print(self.msg_output_format.format(**message_dict))
class ByBitWebsocketConnection: BTCUSD_KLINE_1M = 'kline' + '.BTCUSD.' + '1m' ETHUSD_KLINE_1M = 'kline' + '.ETHUSD.' + '1m' EOSUSD_KLINE_1M = 'kline' + '.EOSUSD.' + '1m' XRPUSD_KLINE_1M = 'kline' + '.XRPUSD.' + '1m' BTCUSD_ORDERBOOK = "orderBookL2_25." + "BTCUSD" ETHUSD_ORDERBOOK = "orderBookL2_25." + "ETHUSD" EOSUSD_ORDERBOOK = "orderBookL2_25." + "EOSUSD" XRPUSD_ORDERBOOK = "orderBookL2_25." + "XRPUSD" POSITION = 'position' EXECUTION = 'execution' ORDER = 'order' # noinspection PyUnresolvedReferences def __init__(self, api_key, secret, use_test_net): self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.INFO) self._api_key = api_key self._secret = secret self.ws_url_main = 'wss://stream.bybit.com/realtime' self.ws_url_test = 'wss://stream-testnet.bybit.com/realtime' self.__hb_count = 0 if not use_test_net: self.ws_url = self.ws_url_main else: self.ws_url = self.ws_url_test self.ws: WebSocketApp self.ws_message_data = { self.BTCUSD_KLINE_1M: {}, self.ETHUSD_KLINE_1M: {}, self.EOSUSD_KLINE_1M: {}, self.XRPUSD_KLINE_1M: {}, self.POSITION: {}, self.EXECUTION: {}, self.ORDER: {}, self.BTCUSD_ORDERBOOK: pd.DataFrame(), self.ETHUSD_ORDERBOOK: pd.DataFrame(), self.EOSUSD_ORDERBOOK: pd.DataFrame(), self.XRPUSD_ORDERBOOK: pd.DataFrame() } self.__lock = threading.RLock() def _connect_websocket(self): self.ws = WebSocketApp(url=self.ws_url, on_open=self.__on_open, on_message=self.__on_message, on_close=self.__on_close) self.wst = threading.Thread(target=lambda: self.ws.run_forever()) self.wst.daemon = True self.wst.start() self.wst.join() conn_timeout = 10 while not self.ws.sock or not self.ws.sock.connected and conn_timeout: time.sleep(1) conn_timeout -= 1 if not conn_timeout: raise websocket.WebSocketTimeoutException( "Couldn't connect to WS! Exiting. Please check your host settings. ") def __on_open(self, ws): expires = int(time.time() * 1000) + 10000 param_str = 'GET/realtime' + str(expires) sign = hmac.new(self._secret.encode('utf-8'), param_str.encode('utf-8'), hashlib.sha256).hexdigest() ws.send(json.dumps( {'op': 'auth', 'args': [self._api_key, expires, sign]})) self.ws.send(json.dumps( {'op': 'subscribe', 'args': [ self.BTCUSD_KLINE_1M, self.ETHUSD_KLINE_1M, self.EOSUSD_KLINE_1M, self.XRPUSD_KLINE_1M, self.POSITION, self.EXECUTION, self.ORDER, self.BTCUSD_ORDERBOOK, self.ETHUSD_ORDERBOOK, self.EOSUSD_ORDERBOOK, self.XRPUSD_ORDERBOOK ]})) def __on_close(self, ws): raise ConnectionError("Connection Closed Unexpectedly.") # noinspection PyUnresolvedReferences def __save_orderbook(self, type, message): if message['type'] == 'snapshot': self.ws_message_data[type] = pd.io.json.json_normalize(message['data']).set_index('id').sort_index( ascending=False) self._save_orderbook_snapshot(self.ws_message_data[type]) elif message['type'] == 'delta': if len(message['data']['delete']) != 0: for x in message['data']['delete']: try: self.ws_message_data[type].drop(index=x['id']) except KeyError: print(colored("%s:INFO: Duplicate orderbookl2_25 delete" % (__name__), "blue")) self._save_orderbook_delta_delete(x) if len(message['data']['update']) != 0: update_list = pd.io.json.json_normalize(message['data']['update']).set_index('id') self.ws_message_data[type].update(update_list) self.ws_message_data[type] = self.ws_message_data[type].sort_index(ascending=False) for x in message['data']['update']: self._save_orderbook_delta_update(x) if len(message['data']['insert']) != 0: insert_list = pd.io.json.json_normalize(message['data']['insert']).set_index('id') self.ws_message_data[type].update(insert_list) self.ws_message_data[type] = self.ws_message_data[type].sort_index(ascending=False) for x in message['data']['insert']: self._save_orderbook_delta_insert(x) # noinspection PyUnresolvedReferences def __on_message(self, ws, message): message = json.loads(message) if "topic" not in message: if message.get("ret_msg") == "pong": print(colored("%s:INFO: Heart Beat Message Received at UTC time %s" % ( str(__name__), datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')), "magenta")) return topic = message.get('topic') self.__hb_count = self.__hb_count + 1 if self.__hb_count >= 5: ws.send(json.dumps({"op": "ping"})) self.__hb_count = 0 else: """we don't need to send the heart beat so frequently. """ print(colored("%s:INFO: New websocket message for topic %s received at UTC time %s" % ( str(__name__), str(topic), datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')), "blue")) if topic == self.BTCUSD_KLINE_1M: self.ws_message_data[self.BTCUSD_KLINE_1M] = message elif topic == self.ETHUSD_KLINE_1M: self.ws_message_data[self.ETHUSD_KLINE_1M] = message elif topic == self.XRPUSD_KLINE_1M: self.ws_message_data[self.XRPUSD_KLINE_1M] = message elif topic == self.EOSUSD_KLINE_1M: self.ws_message_data[self.EOSUSD_KLINE_1M] = message elif topic == self.POSITION: self.ws_message_data[self.POSITION] = message self._save_position(self.ws_message_data[ByBitWebsocketConnection.POSITION]) elif topic == self.EXECUTION: self.ws_message_data[self.EXECUTION] = message self._save_fill_history(self.ws_message_data[ByBitWebsocketConnection.EXECUTION]) elif topic == self.ORDER: self.ws_message_data[self.ORDER] = message self._save_order(self.ws_message_data[ByBitWebsocketConnection.ORDER]) elif topic == self.BTCUSD_ORDERBOOK: self.__save_orderbook(self.BTCUSD_ORDERBOOK, message) elif topic == self.ETHUSD_ORDERBOOK: self.__save_orderbook(self.ETHUSD_ORDERBOOK, message) elif topic == self.EOSUSD_ORDERBOOK: self.__save_orderbook(self.EOSUSD_ORDERBOOK, message) elif topic == self.XRPUSD_ORDERBOOK: self.__save_orderbook(self.XRPUSD_ORDERBOOK, message)
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)
def _send(self, ws: WebSocketApp, msg: dict): msg['reqid'] = self._req_id ws.send(json.dumps(msg)) self._req_id += 1
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()
def hndl(ws: WebSocketApp, msg): if not msg: # Reply back with an empty message when the server sends an empty message ws.send('') return # noinspection PyBroadException try: msg = json.loads(msg) except: self.logger.warning( 'Received invalid JSON message from Trello: {}'.format( msg)) return if 'error' in msg: self.logger.warning('Trello error: {}'.format(msg['error'])) return if msg.get('reqid') == 0: self.logger.debug('Ping response received, subscribing boards') self._initialize_connection(ws) return notify = msg.get('notify') if not notify: return if notify['event'] != 'updateModels' or notify[ 'typeName'] != 'Action': return for delta in notify['deltas']: args = { 'card_id': delta['data']['card']['id'], 'card_name': delta['data']['card']['name'], 'list_id': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('id'), 'list_name': (delta['data'].get('list') or delta['data'].get('listAfter', {})).get('name'), 'board_id': delta['data']['board']['id'], 'board_name': delta['data']['board']['name'], 'closed': delta.get('closed'), 'member_id': delta['memberCreator']['id'], 'member_username': delta['memberCreator']['username'], 'member_fullname': delta['memberCreator']['fullName'], 'date': delta['date'], } if delta.get('type') == 'createCard': self.bus.post(NewCardEvent(**args)) elif delta.get('type') == 'updateCard': if 'listBefore' in delta['data']: args.update({ 'old_list_id': delta['data']['listBefore']['id'], 'old_list_name': delta['data']['listBefore']['name'], }) self.bus.post(MoveCardEvent(**args)) elif 'closed' in delta['data'].get('old', {}): cls = UnarchivedCardEvent if delta['data']['old'][ 'closed'] else ArchivedCardEvent self.bus.post(cls(**args))
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 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)