diff --git a/addon.xml b/addon.xml
index ed8f6743..3881fc4b 100644
--- a/addon.xml
+++ b/addon.xml
@@ -4,6 +4,7 @@
+
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index c2cdd294..fcc80e22 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -1134,11 +1134,6 @@ msgctxt "#39089"
msgid "Alexa connection status:"
msgstr ""
-# PKC Settings - Connection - Background sync connection status
-msgctxt "#39090"
-msgid "Suspended - not connected"
-msgstr ""
-
# PKC Settings - Connection - Background sync connection status
msgctxt "#39091"
msgid "Timeout - not connected"
@@ -1149,6 +1144,16 @@ msgctxt "#39092"
msgid "IOError - not connected"
msgstr ""
+# PKC Settings - Connection - Background sync connection status
+msgctxt "#39093"
+msgid "Suspended - not connected"
+msgstr ""
+
+# PKC Settings - Connection - Background sync connection status
+msgctxt "#39094"
+msgid "Managed Plex User - not connected"
+msgstr ""
+
msgctxt "#39200"
msgid "Log-out Plex Home User "
msgstr ""
diff --git a/resources/lib/app/libsync.py b/resources/lib/app/libsync.py
index c49db72b..371e6194 100644
--- a/resources/lib/app/libsync.py
+++ b/resources/lib/app/libsync.py
@@ -57,8 +57,6 @@ class Sync(object):
# How often shall we sync?
self.full_sync_intervall = None
- # Background Sync disabled?
- self.background_sync_disabled = None
# How long shall we wait with synching a new item to make sure Plex got all
# metadata?
self.backgroundsync_saftymargin = None
@@ -81,7 +79,6 @@ class Sync(object):
# List of section_ids we're synching to Kodi - will be automatically
# re-built if sections are set a-new
self.section_ids = set()
- self.enable_alexa = None
self.load()
@@ -122,8 +119,6 @@ class Sync(object):
Any settings unrelated to syncs to the Kodi database - can thus be
safely reset without a Kodi reboot
"""
- self.background_sync_disabled = utils.settings('enableBackgroundSync') == 'false'
- self.enable_alexa = utils.settings('enable_alexa') == 'true'
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))
diff --git a/resources/lib/service_entry.py b/resources/lib/service_entry.py
index 444d1f53..3b0b7c61 100644
--- a/resources/lib/service_entry.py
+++ b/resources/lib/service_entry.py
@@ -98,7 +98,8 @@ class Service(object):
self.welcome_msg = True
self.connection_check_counter = 0
self.setup = None
- self.alexa = None
+ self.pms_ws = None
+ self.alexa_ws = None
self.playqueue = None
# Flags for other threads
self.connection_check_running = False
@@ -446,8 +447,8 @@ class Service(object):
self.setup.setup()
# Initialize important threads
- self.ws = websocket_client.PMS_Websocket()
- self.alexa = websocket_client.Alexa_Websocket()
+ self.pms_ws = websocket_client.get_pms_websocketapp()
+ self.alexa_ws = websocket_client.get_alexa_websocketapp()
self.sync = sync.Sync()
self.plexcompanion = plex_companion.PlexCompanion()
self.playqueue = playqueue.PlayqueueMonitor()
@@ -547,11 +548,11 @@ class Service(object):
continue
elif not self.startup_completed:
self.startup_completed = True
- self.ws.start()
+ self.pms_ws.start()
self.sync.start()
self.plexcompanion.start()
self.playqueue.start()
- self.alexa.start()
+ self.alexa_ws.start()
elif app.APP.is_playing:
skip_plex_intro.check()
diff --git a/resources/lib/sync.py b/resources/lib/sync.py
index 562363cb..83722d02 100644
--- a/resources/lib/sync.py
+++ b/resources/lib/sync.py
@@ -227,7 +227,7 @@ class Sync(backgroundthread.KillableThread):
not app.APP.is_playing_video):
LOG.info('Doing scheduled full library scan')
self.start_library_sync()
- elif not app.SYNC.background_sync_disabled:
+ else:
# Check back whether we should process something Only do
# this once a while (otherwise, potentially many screen
# refreshes lead to flickering)
diff --git a/resources/lib/websocket.py b/resources/lib/websocket.py
deleted file mode 100644
index 8f9e372b..00000000
--- a/resources/lib/websocket.py
+++ /dev/null
@@ -1,917 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-websocket - WebSocket client library for Python
-
-Copyright (C) 2010 Hiroki Ohtani(liris)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-import socket
-
-try:
- import ssl
- from ssl import SSLError
- HAVE_SSL = True
-except ImportError:
- class SSLError(Exception):
- """
- Dummy class of SSLError for ssl none-support environment.
- """
- pass
-
- HAVE_SSL = False
-
-from urlparse import urlparse
-import os
-import array
-import struct
-import uuid
-import hashlib
-import base64
-import threading
-import logging
-import traceback
-import sys
-
-from . import utils, app
-
-###############################################################################
-
-LOG = logging.getLogger('PLEX.websocket')
-
-###############################################################################
-
-"""
-websocket python client.
-=========================
-
-This version support only hybi-13.
-Please see http://tools.ietf.org/html/rfc6455 for protocol.
-"""
-
-
-# websocket supported version.
-VERSION = 13
-
-# closing frame status codes.
-STATUS_NORMAL = 1000
-STATUS_GOING_AWAY = 1001
-STATUS_PROTOCOL_ERROR = 1002
-STATUS_UNSUPPORTED_DATA_TYPE = 1003
-STATUS_STATUS_NOT_AVAILABLE = 1005
-STATUS_ABNORMAL_CLOSED = 1006
-STATUS_INVALID_PAYLOAD = 1007
-STATUS_POLICY_VIOLATION = 1008
-STATUS_MESSAGE_TOO_BIG = 1009
-STATUS_INVALID_EXTENSION = 1010
-STATUS_UNEXPECTED_CONDITION = 1011
-STATUS_TLS_HANDSHAKE_ERROR = 1015
-
-
-class WebSocketException(Exception):
- """
- websocket exeception class.
- """
- pass
-
-
-class WebSocketConnectionClosedException(WebSocketException):
- """
- If remote host closed the connection or some network error happened,
- this exception will be raised.
- """
- pass
-
-
-class WebSocketTimeoutException(WebSocketException):
- """
- WebSocketTimeoutException will be raised at socket timeout during read and
- write data.
- """
- pass
-
-
-class WebsocketRedirect(WebSocketException):
- """
- WebsocketRedirect will be raised if a status code 301 is returned
- The Exception will be instantiated with a dict containing all response
- headers; which should contain the redirect address under the key 'location'
-
- Access the headers via the attribute headers
- """
- def __init__(self, headers):
- self.headers = headers
- super(WebsocketRedirect, self).__init__()
-
-
-DEFAULT_TIMEOUT = None
-TRACE_ENABLED = False
-
-
-def enable_trace(tracable):
- """
- turn on/off the tracability.
-
- tracable: boolean value. if set True, tracability is enabled.
- """
- global TRACE_ENABLED
- TRACE_ENABLED = tracable
- if tracable:
- if not LOG.handlers:
- LOG.addHandler(logging.StreamHandler())
- LOG.setLevel(logging.DEBUG)
-
-
-def setdefaulttimeout(timeout):
- """
- Set the global timeout setting to connect.
-
- timeout: default socket timeout time. This value is second.
- """
- global DEFAULT_TIMEOUT
- DEFAULT_TIMEOUT = timeout
-
-
-def getdefaulttimeout():
- """
- Return the global timeout setting(second) to connect.
- """
- return DEFAULT_TIMEOUT
-
-
-def _parse_url(url):
- """
- parse url and the result is tuple of
- (hostname, port, resource path and the flag of secure mode)
-
- url: url string.
- """
- if ":" not in url:
- raise ValueError("url is invalid")
-
- scheme, url = url.split(":", 1)
-
- parsed = urlparse(url, scheme="http")
- if parsed.hostname:
- hostname = parsed.hostname
- else:
- raise ValueError("hostname is invalid")
- port = 0
- if parsed.port:
- port = parsed.port
-
- is_secure = False
- if scheme == "ws" or scheme == 'http':
- if not port:
- port = 80
- elif scheme == "wss" or scheme == 'https':
- is_secure = True
- if not port:
- port = 443
- else:
- raise ValueError("scheme %s is invalid" % scheme)
-
- if parsed.path:
- resource = parsed.path
- else:
- resource = "/"
-
- if parsed.query:
- resource += "?" + parsed.query
-
- return (hostname, port, resource, is_secure)
-
-
-def create_connection(url, timeout=None, **options):
- """
- connect to url and return websocket object.
-
- Connect to url and return the WebSocket object.
- Passing optional timeout parameter will set the timeout on the socket.
- If no timeout is supplied, the global default timeout setting returned by
- getdefauttimeout() is used.
- You can customize using 'options'.
- If you set "header" list object, you can set your own custom header.
-
- >>> conn = create_connection("ws://echo.websocket.org/",
- ... header=["User-Agent: MyProgram",
- ... "x-custom: header"])
-
-
- timeout: socket timeout time. This value is integer.
- if you set None for this value, it means "use DEFAULT_TIMEOUT
- value"
-
- options: current support option is only "header".
- if you set header as dict value, the custom HTTP headers are added
- """
- sockopt = options.get("sockopt", [])
- sslopt = options.get("sslopt", {})
- websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
- websock.settimeout(timeout if timeout is not None else DEFAULT_TIMEOUT)
- websock.connect(url, **options)
- return websock
-
-
-_MAX_INTEGER = (1 << 32) - 1
-_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
-_MAX_CHAR_BYTE = (1 << 8) - 1
-
-# ref. Websocket gets an update, and it breaks stuff.
-# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
-
-
-def _create_sec_websocket_key():
- uid = uuid.uuid4()
- return base64.encodestring(uid.bytes).strip()
-
-
-_HEADERS_TO_CHECK = {"upgrade": "websocket", "connection": "upgrade"}
-
-
-class ABNF(object):
- """
- ABNF frame class.
- see http://tools.ietf.org/html/rfc5234
- and http://tools.ietf.org/html/rfc6455#section-5.2
- """
-
- # operation code values.
- OPCODE_CONT = 0x0
- OPCODE_TEXT = 0x1
- OPCODE_BINARY = 0x2
- OPCODE_CLOSE = 0x8
- OPCODE_PING = 0x9
- OPCODE_PONG = 0xa
-
- # available operation code value tuple
- OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
- OPCODE_PING, OPCODE_PONG)
-
- # opcode human readable string
- OPCODE_MAP = {
- OPCODE_CONT: "cont",
- OPCODE_TEXT: "text",
- OPCODE_BINARY: "binary",
- OPCODE_CLOSE: "close",
- OPCODE_PING: "ping",
- OPCODE_PONG: "pong"
- }
-
- # data length threashold.
- LENGTH_7 = 0x7d
- LENGTH_16 = 1 << 16
- LENGTH_63 = 1 << 63
-
- def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
- opcode=OPCODE_TEXT, mask=1, data=""):
- """
- Constructor for ABNF.
- please check RFC for arguments.
- """
- self.fin = fin
- self.rsv1 = rsv1
- self.rsv2 = rsv2
- self.rsv3 = rsv3
- self.opcode = opcode
- self.mask = mask
- self.data = data
- self.get_mask_key = os.urandom
-
- def __str__(self):
- return "fin=" + str(self.fin) \
- + " opcode=" + str(self.opcode) \
- + " data=" + str(self.data)
-
- @staticmethod
- def create_frame(data, opcode):
- """
- create frame to send text, binary and other data.
-
- data: data to send. This is string value(byte array).
- if opcode is OPCODE_TEXT and this value is uniocde,
- data value is conveted into unicode string, automatically.
-
- opcode: operation code. please see OPCODE_XXX.
- """
- if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
- data = utils.try_encode(data)
- # mask must be set if send data from client
- return ABNF(1, 0, 0, 0, opcode, 1, data)
-
- def format(self):
- """
- format this object to string(byte array) to send data to server.
- """
- if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
- raise ValueError("not 0 or 1")
- if self.opcode not in ABNF.OPCODES:
- raise ValueError("Invalid OPCODE")
- length = len(self.data)
- if length >= ABNF.LENGTH_63:
- raise ValueError("data is too long")
-
- frame_header = chr(self.fin << 7 |
- self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 |
- self.opcode)
- if length < ABNF.LENGTH_7:
- frame_header += chr(self.mask << 7 | length)
- elif length < ABNF.LENGTH_16:
- frame_header += chr(self.mask << 7 | 0x7e)
- frame_header += struct.pack("!H", length)
- else:
- frame_header += chr(self.mask << 7 | 0x7f)
- frame_header += struct.pack("!Q", length)
-
- if not self.mask:
- return frame_header + self.data
- else:
- mask_key = self.get_mask_key(4)
- return frame_header + self._get_masked(mask_key)
-
- def _get_masked(self, mask_key):
- s = ABNF.mask(mask_key, self.data)
- return mask_key + "".join(s)
-
- @staticmethod
- def mask(mask_key, data):
- """
- mask or unmask data. Just do xor for each byte
-
- mask_key: 4 byte string(byte).
-
- data: data to mask/unmask.
- """
- _m = array.array("B", mask_key)
- _d = array.array("B", data)
- for i in xrange(len(_d)):
- _d[i] ^= _m[i % 4]
- return _d.tostring()
-
-
-class WebSocket(object):
- """
- Low level WebSocket interface.
- This class is based on
- The WebSocket protocol draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
-
- We can connect to the websocket server and send/recieve data.
- The following example is a echo client.
-
- >>> import websocket
- >>> ws = websocket.WebSocket()
- >>> ws.connect("ws://echo.websocket.org")
- >>> ws.send("Hello, Server")
- >>> ws.recv()
- 'Hello, Server'
- >>> ws.close()
-
- get_mask_key: a callable to produce new mask keys, see the set_mask_key
- function's docstring for more details
- sockopt: values for socket.setsockopt.
- sockopt must be tuple and each element is argument of sock.setscokopt.
- sslopt: dict object for ssl socket option.
- """
-
- def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
- """
- Initalize WebSocket object.
- """
- if sockopt is None:
- sockopt = []
- if sslopt is None:
- sslopt = {}
- self.connected = False
- self.sock = socket.socket()
- for opts in sockopt:
- self.sock.setsockopt(*opts)
- self.sslopt = sslopt
- self.get_mask_key = get_mask_key
- # Buffers over the packets from the layer beneath until desired amount
- # bytes of bytes are received.
- self._recv_buffer = []
- # These buffer over the build-up of a single frame.
- self._frame_header = None
- self._frame_length = None
- self._frame_mask = None
- self._cont_data = None
-
- def fileno(self):
- """
- Returns sock.fileno()
- """
- return self.sock.fileno()
-
- def set_mask_key(self, func):
- """
- set function to create musk key. You can custumize mask key generator.
- Mainly, this is for testing purpose.
-
- func: callable object. the fuct must 1 argument as integer.
- The argument means length of mask key.
- This func must be return string(byte array),
- which length is argument specified.
- """
- self.get_mask_key = func
-
- def gettimeout(self):
- """
- Get the websocket timeout(second).
- """
- return self.sock.gettimeout()
-
- def settimeout(self, timeout):
- """
- Set the timeout to the websocket.
-
- timeout: timeout time(second).
- """
- self.sock.settimeout(timeout)
-
- timeout = property(gettimeout, settimeout)
-
- def connect(self, url, **options):
- """
- Connect to url. url is websocket url scheme. ie. ws://host:port/resource
- You can customize using 'options'.
- If you set "header" dict object, you can set your own custom header.
-
- >>> ws = WebSocket()
- >>> ws.connect("ws://echo.websocket.org/",
- ... header={"User-Agent: MyProgram",
- ... "x-custom: header"})
-
- timeout: socket timeout time. This value is integer.
- if you set None for this value,
- it means "use DEFAULT_TIMEOUT value"
-
- options: current support option is only "header".
- if you set header as dict value,
- the custom HTTP headers are added.
-
- """
- hostname, port, resource, is_secure = _parse_url(url)
- # TODO: we need to support proxy
- self.sock.connect((hostname, port))
- if is_secure:
- if HAVE_SSL:
- if self.sslopt is None:
- sslopt = {}
- else:
- sslopt = self.sslopt
- self.sock = ssl.wrap_socket(self.sock, **sslopt)
- else:
- raise WebSocketException("SSL not available.")
-
- self._handshake(hostname, port, resource, **options)
-
- def _handshake(self, host, port, resource, **options):
- headers = []
- headers.append("GET %s HTTP/1.1" % resource)
- headers.append("Upgrade: websocket")
- headers.append("Connection: Upgrade")
- if port == 80:
- hostport = host
- else:
- hostport = "%s:%d" % (host, port)
- headers.append("Host: %s" % hostport)
-
- if "origin" in options:
- headers.append("Origin: %s" % options["origin"])
- else:
- headers.append("Origin: http://%s" % hostport)
-
- key = _create_sec_websocket_key()
- headers.append("Sec-WebSocket-Key: %s" % key)
- headers.append("Sec-WebSocket-Version: %s" % VERSION)
- if "header" in options:
- headers.extend(options["header"])
-
- headers.append("")
- headers.append("")
-
- header_str = "\r\n".join(headers)
- self._send(header_str)
- if TRACE_ENABLED:
- LOG.debug("--- request header ---")
- LOG.debug(header_str)
- LOG.debug("-----------------------")
-
- status, resp_headers = self._read_headers()
- if status == 301:
- # Redirect
- raise WebsocketRedirect(resp_headers)
- if status != 101:
- self.close()
- raise WebSocketException("Handshake Status %d" % status)
-
- success = self._validate_header(resp_headers, key)
- if not success:
- self.close()
- raise WebSocketException("Invalid WebSocket Header")
-
- self.connected = True
-
- @staticmethod
- def _validate_header(headers, key):
- for k, v in _HEADERS_TO_CHECK.iteritems():
- r = headers.get(k, None)
- if not r:
- return False
- r = r.lower()
- if v != r:
- return False
-
- result = headers.get("sec-websocket-accept", None)
- if not result:
- return False
- result = result.lower()
-
- value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
- return hashed == result
-
- def _read_headers(self):
- status = None
- headers = {}
- if TRACE_ENABLED:
- LOG.debug("--- response header ---")
-
- while True:
- line = self._recv_line()
- if line == "\r\n":
- break
- line = line.strip()
- if TRACE_ENABLED:
- LOG.debug(line)
- if not status:
- status_info = line.split(" ", 2)
- status = int(status_info[1])
- else:
- kv = line.split(":", 1)
- if len(kv) == 2:
- key, value = kv
- headers[key.lower()] = value.strip().lower()
- else:
- raise WebSocketException("Invalid header")
-
- if TRACE_ENABLED:
- LOG.debug("-----------------------")
-
- return status, headers
-
- def send(self, payload, opcode=ABNF.OPCODE_TEXT):
- """
- Send the data as string.
-
- payload: Payload must be utf-8 string or unicoce,
- if the opcode is OPCODE_TEXT.
- Otherwise, it must be string(byte array)
-
- opcode: operation code to send. Please see OPCODE_XXX.
- """
- frame = ABNF.create_frame(payload, opcode)
- if self.get_mask_key:
- frame.get_mask_key = self.get_mask_key
- data = frame.format()
- length = len(data)
- if TRACE_ENABLED:
- LOG.debug("send: %s", repr(data))
- while data:
- l = self._send(data)
- data = data[l:]
- return length
-
- def send_binary(self, payload):
- """
- send the payload
- """
- return self.send(payload, ABNF.OPCODE_BINARY)
-
- def ping(self, payload=""):
- """
- send ping data.
-
- payload: data payload to send server.
- """
- self.send(payload, ABNF.OPCODE_PING)
-
- def pong(self, payload):
- """
- send pong data.
-
- payload: data payload to send server.
- """
- self.send(payload, ABNF.OPCODE_PONG)
-
- def recv(self):
- """
- Receive string data(byte array) from the server.
-
- return value: string(byte array) value.
- """
- _, data = self.recv_data()
- return data
-
- def recv_data(self):
- """
- Recieve data with operation code.
-
- return value: tuple of operation code and string(byte array) value.
- """
- while True:
- frame = self.recv_frame()
- if not frame:
- # handle error:
- # 'NoneType' object has no attribute 'opcode'
- raise WebSocketException("Not a valid frame %s" % frame)
- elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
- if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
- raise WebSocketException("Illegal frame")
- if self._cont_data:
- self._cont_data[1] += frame.data
- else:
- self._cont_data = [frame.opcode, frame.data]
- if frame.fin:
- data = self._cont_data
- self._cont_data = None
- return data
- elif frame.opcode == ABNF.OPCODE_CLOSE:
- self.send_close()
- return (frame.opcode, None)
- elif frame.opcode == ABNF.OPCODE_PING:
- self.pong(frame.data)
-
- def recv_frame(self):
- """
- recieve data as frame from server.
-
- return value: ABNF frame object.
- """
- # Header
- if self._frame_header is None:
- self._frame_header = self._recv_strict(2)
- b1 = ord(self._frame_header[0])
- fin = b1 >> 7 & 1
- rsv1 = b1 >> 6 & 1
- rsv2 = b1 >> 5 & 1
- rsv3 = b1 >> 4 & 1
- opcode = b1 & 0xf
- b2 = ord(self._frame_header[1])
- has_mask = b2 >> 7 & 1
- # Frame length
- if self._frame_length is None:
- length_bits = b2 & 0x7f
- if length_bits == 0x7e:
- length_data = self._recv_strict(2)
- self._frame_length = struct.unpack("!H", length_data)[0]
- elif length_bits == 0x7f:
- length_data = self._recv_strict(8)
- self._frame_length = struct.unpack("!Q", length_data)[0]
- else:
- self._frame_length = length_bits
- # Mask
- if self._frame_mask is None:
- self._frame_mask = self._recv_strict(4) if has_mask else ""
- # Payload
- payload = self._recv_strict(self._frame_length)
- if has_mask:
- payload = ABNF.mask(self._frame_mask, payload)
- # Reset for next frame
- self._frame_header = None
- self._frame_length = None
- self._frame_mask = None
- return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
-
-
- def send_close(self, status=STATUS_NORMAL, reason=""):
- """
- send close data to the server.
-
- status: status code to send. see STATUS_XXX.
-
- reason: the reason to close. This must be string.
- """
- if status < 0 or status >= ABNF.LENGTH_16:
- raise ValueError("code is invalid range")
- self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
-
- def close(self, status=STATUS_NORMAL, reason=""):
- """
- Close Websocket object
-
- status: status code to send. see STATUS_XXX.
-
- reason: the reason to close. This must be string.
- """
- try:
- self.sock.shutdown(socket.SHUT_RDWR)
- except Exception:
- pass
- self._closeInternal()
-
- def _closeInternal(self):
- self.connected = False
- self.sock.close()
-
- def _send(self, data):
- try:
- return self.sock.send(data)
- except socket.timeout as e:
- raise WebSocketTimeoutException(e.args[0])
- except Exception as e:
- if "timed out" in e.args[0]:
- raise WebSocketTimeoutException(e.args[0])
- else:
- raise e
-
- def _recv(self, bufsize):
- try:
- bytes_ = self.sock.recv(bufsize)
- except socket.timeout as e:
- raise WebSocketTimeoutException(e.args[0])
- except SSLError as e:
- if e.args[0] == "The read operation timed out":
- raise WebSocketTimeoutException(e.args[0])
- else:
- raise
- if not bytes_:
- raise WebSocketConnectionClosedException()
- return bytes_
-
- def _recv_strict(self, bufsize):
- shortage = bufsize - sum(len(x) for x in self._recv_buffer)
- while shortage > 0:
- bytes_ = self._recv(shortage)
- self._recv_buffer.append(bytes_)
- shortage -= len(bytes_)
- unified = "".join(self._recv_buffer)
- if shortage == 0:
- self._recv_buffer = []
- return unified
- else:
- self._recv_buffer = [unified[bufsize:]]
- return unified[:bufsize]
-
- def _recv_line(self):
- line = []
- while True:
- c = self._recv(1)
- line.append(c)
- if c == "\n":
- break
- return "".join(line)
-
-
-class WebSocketApp(object):
- """
- Higher level of APIs are provided.
- The interface is like JavaScript WebSocket object.
- """
- def __init__(self, url, header=None,
- on_open=None, on_message=None, on_error=None,
- on_close=None, keep_running=True, get_mask_key=None):
- """
- url: websocket url.
- header: custom header for websocket handshake.
- on_open: callable object which is called at opening websocket.
- this function has one argument. The arugment is this class object.
- on_message: callbale object which is called when recieved data.
- on_message has 2 arguments.
- The 1st arugment is this class object.
- The passing 2nd arugment is utf-8 string which we get from the server.
- on_error: callable object which is called when we get error.
- on_error has 2 arguments.
- The 1st arugment is this class object.
- The passing 2nd arugment is exception object.
- on_close: callable object which is called when closed the connection.
- this function has one argument. The arugment is this class object.
- keep_running: a boolean flag indicating whether the app's main loop should
- keep running, defaults to True
- get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
- docstring for more information
- """
- self.url = url
- self.header = [] if header is None else header
- self.on_open = on_open
- self.on_message = on_message
- self.on_error = on_error
- self.on_close = on_close
- self.keep_running = keep_running
- self.get_mask_key = get_mask_key
- self.sock = None
-
- def send(self, data, opcode=ABNF.OPCODE_TEXT):
- """
- send message.
- data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
- opcode: operation code of data. default is OPCODE_TEXT.
- """
- if self.sock.send(data, opcode) == 0:
- raise WebSocketConnectionClosedException()
-
- def close(self):
- """
- close websocket connection.
- """
- self.keep_running = False
- if self.sock != None:
- self.sock.close()
-
- def _send_ping(self, interval):
- while True:
- for _ in range(interval):
- app.APP.monitor.waitForAbort(1)
- if not self.keep_running:
- return
- self.sock.ping()
-
- def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
- """
- run event loop for WebSocket framework.
- This loop is infinite loop and is alive during websocket is available.
- sockopt: values for socket.setsockopt.
- sockopt must be tuple and each element is argument of
- sock.setscokopt.
- sslopt: ssl socket optional dict.
- ping_interval: automatically send "ping" command every specified
- period(second)
- if set to 0, not send automatically.
- """
- if sockopt is None:
- sockopt = []
- if sslopt is None:
- sslopt = {}
- if self.sock:
- raise WebSocketException("socket is already opened")
- thread = None
- self.keep_running = True
-
- try:
- self.sock = WebSocket(self.get_mask_key,
- sockopt=sockopt,
- sslopt=sslopt)
- self.sock.settimeout(DEFAULT_TIMEOUT)
- self.sock.connect(self.url, header=self.header)
- self._callback(self.on_open)
-
- if ping_interval:
- thread = threading.Thread(target=self._send_ping,
- args=(ping_interval,))
- thread.setDaemon(True)
- thread.start()
-
- while self.keep_running:
- try:
- data = self.sock.recv()
- if data is None or self.keep_running is False:
- break
- self._callback(self.on_message, data)
- except Exception, e:
- if "timed out" not in e.args[0]:
- raise e
-
- except Exception, e:
- self._callback(self.on_error, e)
- finally:
- if thread:
- self.keep_running = False
- self.sock.close()
- self._callback(self.on_close)
- self.sock = None
-
- def _callback(self, callback, *args):
- if callback:
- try:
- callback(self, *args)
- except Exception, e:
- LOG.error(e)
- _, _, tb = sys.exc_info()
- traceback.print_tb(tb)
-
-
-if __name__ == "__main__":
- enable_trace(True)
- WEBSOCKET = create_connection("ws://echo.websocket.org/")
- LOG.info("Sending 'Hello, World'...")
- WEBSOCKET.send("Hello, World")
- LOG.info("Sent")
- LOG.info("Receiving...")
- RESULT = WEBSOCKET.recv()
- LOG.info("Received '%s'", RESULT)
- WEBSOCKET.close()
diff --git a/resources/lib/websocket/__init__.py b/resources/lib/websocket/__init__.py
new file mode 100644
index 00000000..ce1b7873
--- /dev/null
+++ b/resources/lib/websocket/__init__.py
@@ -0,0 +1,28 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+from ._abnf import *
+from ._app import WebSocketApp
+from ._core import *
+from ._exceptions import *
+from ._logging import *
+from ._socket import *
+
+__version__ = "0.58.0"
diff --git a/resources/lib/websocket/_abnf.py b/resources/lib/websocket/_abnf.py
new file mode 100644
index 00000000..cc66704a
--- /dev/null
+++ b/resources/lib/websocket/_abnf.py
@@ -0,0 +1,458 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import array
+import os
+import struct
+
+import six
+
+from ._exceptions import *
+from ._utils import validate_utf8
+from threading import Lock
+
+try:
+ if six.PY3:
+ import numpy
+ else:
+ numpy = None
+except ImportError:
+ numpy = None
+
+try:
+ # If wsaccel is available we use compiled routines to mask data.
+ if not numpy:
+ from wsaccel.xormask import XorMaskerSimple
+
+ def _mask(_m, _d):
+ return XorMaskerSimple(_m).process(_d)
+except ImportError:
+ # wsaccel is not available, we rely on python implementations.
+ def _mask(_m, _d):
+ for i in range(len(_d)):
+ _d[i] ^= _m[i % 4]
+
+ if six.PY3:
+ return _d.tobytes()
+ else:
+ return _d.tostring()
+
+
+__all__ = [
+ 'ABNF', 'continuous_frame', 'frame_buffer',
+ 'STATUS_NORMAL',
+ 'STATUS_GOING_AWAY',
+ 'STATUS_PROTOCOL_ERROR',
+ 'STATUS_UNSUPPORTED_DATA_TYPE',
+ 'STATUS_STATUS_NOT_AVAILABLE',
+ 'STATUS_ABNORMAL_CLOSED',
+ 'STATUS_INVALID_PAYLOAD',
+ 'STATUS_POLICY_VIOLATION',
+ 'STATUS_MESSAGE_TOO_BIG',
+ 'STATUS_INVALID_EXTENSION',
+ 'STATUS_UNEXPECTED_CONDITION',
+ 'STATUS_BAD_GATEWAY',
+ 'STATUS_TLS_HANDSHAKE_ERROR',
+]
+
+# closing frame status codes.
+STATUS_NORMAL = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA_TYPE = 1003
+STATUS_STATUS_NOT_AVAILABLE = 1005
+STATUS_ABNORMAL_CLOSED = 1006
+STATUS_INVALID_PAYLOAD = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_INVALID_EXTENSION = 1010
+STATUS_UNEXPECTED_CONDITION = 1011
+STATUS_BAD_GATEWAY = 1014
+STATUS_TLS_HANDSHAKE_ERROR = 1015
+
+VALID_CLOSE_STATUS = (
+ STATUS_NORMAL,
+ STATUS_GOING_AWAY,
+ STATUS_PROTOCOL_ERROR,
+ STATUS_UNSUPPORTED_DATA_TYPE,
+ STATUS_INVALID_PAYLOAD,
+ STATUS_POLICY_VIOLATION,
+ STATUS_MESSAGE_TOO_BIG,
+ STATUS_INVALID_EXTENSION,
+ STATUS_UNEXPECTED_CONDITION,
+ STATUS_BAD_GATEWAY,
+)
+
+
+class ABNF(object):
+ """
+ ABNF frame class.
+ See http://tools.ietf.org/html/rfc5234
+ and http://tools.ietf.org/html/rfc6455#section-5.2
+ """
+
+ # operation code values.
+ OPCODE_CONT = 0x0
+ OPCODE_TEXT = 0x1
+ OPCODE_BINARY = 0x2
+ OPCODE_CLOSE = 0x8
+ OPCODE_PING = 0x9
+ OPCODE_PONG = 0xa
+
+ # available operation code value tuple
+ OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
+ OPCODE_PING, OPCODE_PONG)
+
+ # opcode human readable string
+ OPCODE_MAP = {
+ OPCODE_CONT: "cont",
+ OPCODE_TEXT: "text",
+ OPCODE_BINARY: "binary",
+ OPCODE_CLOSE: "close",
+ OPCODE_PING: "ping",
+ OPCODE_PONG: "pong"
+ }
+
+ # data length threshold.
+ LENGTH_7 = 0x7e
+ LENGTH_16 = 1 << 16
+ LENGTH_63 = 1 << 63
+
+ def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
+ opcode=OPCODE_TEXT, mask=1, data=""):
+ """
+ Constructor for ABNF. Please check RFC for arguments.
+ """
+ self.fin = fin
+ self.rsv1 = rsv1
+ self.rsv2 = rsv2
+ self.rsv3 = rsv3
+ self.opcode = opcode
+ self.mask = mask
+ if data is None:
+ data = ""
+ self.data = data
+ self.get_mask_key = os.urandom
+
+ def validate(self, skip_utf8_validation=False):
+ """
+ Validate the ABNF frame.
+
+ Parameters
+ ----------
+ skip_utf8_validation: skip utf8 validation.
+ """
+ if self.rsv1 or self.rsv2 or self.rsv3:
+ raise WebSocketProtocolException("rsv is not implemented, yet")
+
+ if self.opcode not in ABNF.OPCODES:
+ raise WebSocketProtocolException("Invalid opcode %r", self.opcode)
+
+ if self.opcode == ABNF.OPCODE_PING and not self.fin:
+ raise WebSocketProtocolException("Invalid ping frame.")
+
+ if self.opcode == ABNF.OPCODE_CLOSE:
+ l = len(self.data)
+ if not l:
+ return
+ if l == 1 or l >= 126:
+ raise WebSocketProtocolException("Invalid close frame.")
+ if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]):
+ raise WebSocketProtocolException("Invalid close frame.")
+
+ code = 256 * \
+ six.byte2int(self.data[0:1]) + six.byte2int(self.data[1:2])
+ if not self._is_valid_close_status(code):
+ raise WebSocketProtocolException("Invalid close opcode.")
+
+ @staticmethod
+ def _is_valid_close_status(code):
+ return code in VALID_CLOSE_STATUS or (3000 <= code < 5000)
+
+ def __str__(self):
+ return "fin=" + str(self.fin) \
+ + " opcode=" + str(self.opcode) \
+ + " data=" + str(self.data)
+
+ @staticmethod
+ def create_frame(data, opcode, fin=1):
+ """
+ Create frame to send text, binary and other data.
+
+ Parameters
+ ----------
+ data:
+ data to send. This is string value(byte array).
+ If opcode is OPCODE_TEXT and this value is unicode,
+ data value is converted into unicode string, automatically.
+ opcode:
+ operation code. please see OPCODE_XXX.
+ fin:
+ fin flag. if set to 0, create continue fragmentation.
+ """
+ if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type):
+ data = data.encode("utf-8")
+ # mask must be set if send data from client
+ return ABNF(fin, 0, 0, 0, opcode, 1, data)
+
+ def format(self):
+ """
+ Format this object to string(byte array) to send data to server.
+ """
+ if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
+ raise ValueError("not 0 or 1")
+ if self.opcode not in ABNF.OPCODES:
+ raise ValueError("Invalid OPCODE")
+ length = len(self.data)
+ if length >= ABNF.LENGTH_63:
+ raise ValueError("data is too long")
+
+ frame_header = chr(self.fin << 7
+ | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
+ | self.opcode)
+ if length < ABNF.LENGTH_7:
+ frame_header += chr(self.mask << 7 | length)
+ frame_header = six.b(frame_header)
+ elif length < ABNF.LENGTH_16:
+ frame_header += chr(self.mask << 7 | 0x7e)
+ frame_header = six.b(frame_header)
+ frame_header += struct.pack("!H", length)
+ else:
+ frame_header += chr(self.mask << 7 | 0x7f)
+ frame_header = six.b(frame_header)
+ frame_header += struct.pack("!Q", length)
+
+ if not self.mask:
+ return frame_header + self.data
+ else:
+ mask_key = self.get_mask_key(4)
+ return frame_header + self._get_masked(mask_key)
+
+ def _get_masked(self, mask_key):
+ s = ABNF.mask(mask_key, self.data)
+
+ if isinstance(mask_key, six.text_type):
+ mask_key = mask_key.encode('utf-8')
+
+ return mask_key + s
+
+ @staticmethod
+ def mask(mask_key, data):
+ """
+ Mask or unmask data. Just do xor for each byte
+
+ Parameters
+ ----------
+ mask_key:
+ 4 byte string(byte).
+ data:
+ data to mask/unmask.
+ """
+ if data is None:
+ data = ""
+
+ if isinstance(mask_key, six.text_type):
+ mask_key = six.b(mask_key)
+
+ if isinstance(data, six.text_type):
+ data = six.b(data)
+
+ if numpy:
+ origlen = len(data)
+ _mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0]
+
+ # We need data to be a multiple of four...
+ data += bytes(" " * (4 - (len(data) % 4)), "us-ascii")
+ a = numpy.frombuffer(data, dtype="uint32")
+ masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32")
+ if len(data) > origlen:
+ return masked.tobytes()[:origlen]
+ return masked.tobytes()
+ else:
+ _m = array.array("B", mask_key)
+ _d = array.array("B", data)
+ return _mask(_m, _d)
+
+
+class frame_buffer(object):
+ _HEADER_MASK_INDEX = 5
+ _HEADER_LENGTH_INDEX = 6
+
+ def __init__(self, recv_fn, skip_utf8_validation):
+ self.recv = recv_fn
+ self.skip_utf8_validation = skip_utf8_validation
+ # Buffers over the packets from the layer beneath until desired amount
+ # bytes of bytes are received.
+ self.recv_buffer = []
+ self.clear()
+ self.lock = Lock()
+
+ def clear(self):
+ self.header = None
+ self.length = None
+ self.mask = None
+
+ def has_received_header(self):
+ return self.header is None
+
+ def recv_header(self):
+ header = self.recv_strict(2)
+ b1 = header[0]
+
+ if six.PY2:
+ b1 = ord(b1)
+
+ fin = b1 >> 7 & 1
+ rsv1 = b1 >> 6 & 1
+ rsv2 = b1 >> 5 & 1
+ rsv3 = b1 >> 4 & 1
+ opcode = b1 & 0xf
+ b2 = header[1]
+
+ if six.PY2:
+ b2 = ord(b2)
+
+ has_mask = b2 >> 7 & 1
+ length_bits = b2 & 0x7f
+
+ self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits)
+
+ def has_mask(self):
+ if not self.header:
+ return False
+ return self.header[frame_buffer._HEADER_MASK_INDEX]
+
+ def has_received_length(self):
+ return self.length is None
+
+ def recv_length(self):
+ bits = self.header[frame_buffer._HEADER_LENGTH_INDEX]
+ length_bits = bits & 0x7f
+ if length_bits == 0x7e:
+ v = self.recv_strict(2)
+ self.length = struct.unpack("!H", v)[0]
+ elif length_bits == 0x7f:
+ v = self.recv_strict(8)
+ self.length = struct.unpack("!Q", v)[0]
+ else:
+ self.length = length_bits
+
+ def has_received_mask(self):
+ return self.mask is None
+
+ def recv_mask(self):
+ self.mask = self.recv_strict(4) if self.has_mask() else ""
+
+ def recv_frame(self):
+
+ with self.lock:
+ # Header
+ if self.has_received_header():
+ self.recv_header()
+ (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header
+
+ # Frame length
+ if self.has_received_length():
+ self.recv_length()
+ length = self.length
+
+ # Mask
+ if self.has_received_mask():
+ self.recv_mask()
+ mask = self.mask
+
+ # Payload
+ payload = self.recv_strict(length)
+ if has_mask:
+ payload = ABNF.mask(mask, payload)
+
+ # Reset for next frame
+ self.clear()
+
+ frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
+ frame.validate(self.skip_utf8_validation)
+
+ return frame
+
+ def recv_strict(self, bufsize):
+ shortage = bufsize - sum(len(x) for x in self.recv_buffer)
+ while shortage > 0:
+ # Limit buffer size that we pass to socket.recv() to avoid
+ # fragmenting the heap -- the number of bytes recv() actually
+ # reads is limited by socket buffer and is relatively small,
+ # yet passing large numbers repeatedly causes lots of large
+ # buffers allocated and then shrunk, which results in
+ # fragmentation.
+ bytes_ = self.recv(min(16384, shortage))
+ self.recv_buffer.append(bytes_)
+ shortage -= len(bytes_)
+
+ unified = six.b("").join(self.recv_buffer)
+
+ if shortage == 0:
+ self.recv_buffer = []
+ return unified
+ else:
+ self.recv_buffer = [unified[bufsize:]]
+ return unified[:bufsize]
+
+
+class continuous_frame(object):
+
+ def __init__(self, fire_cont_frame, skip_utf8_validation):
+ self.fire_cont_frame = fire_cont_frame
+ self.skip_utf8_validation = skip_utf8_validation
+ self.cont_data = None
+ self.recving_frames = None
+
+ def validate(self, frame):
+ if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT:
+ raise WebSocketProtocolException("Illegal frame")
+ if self.recving_frames and \
+ frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
+ raise WebSocketProtocolException("Illegal frame")
+
+ def add(self, frame):
+ if self.cont_data:
+ self.cont_data[1] += frame.data
+ else:
+ if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
+ self.recving_frames = frame.opcode
+ self.cont_data = [frame.opcode, frame.data]
+
+ if frame.fin:
+ self.recving_frames = None
+
+ def is_fire(self, frame):
+ return frame.fin or self.fire_cont_frame
+
+ def extract(self, frame):
+ data = self.cont_data
+ self.cont_data = None
+ frame.data = data[1]
+ if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data):
+ raise WebSocketPayloadException(
+ "cannot decode: " + repr(frame.data))
+
+ return [data[0], frame]
diff --git a/resources/lib/websocket/_app.py b/resources/lib/websocket/_app.py
new file mode 100644
index 00000000..748afd82
--- /dev/null
+++ b/resources/lib/websocket/_app.py
@@ -0,0 +1,394 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import inspect
+import select
+import sys
+import threading
+import time
+import traceback
+
+import six
+
+from ._abnf import ABNF
+from ._core import WebSocket, getdefaulttimeout
+from ._exceptions import *
+from . import _logging
+
+
+__all__ = ["WebSocketApp"]
+
+class Dispatcher:
+ """
+ Dispatcher
+ """
+ def __init__(self, app, ping_timeout):
+ self.app = app
+ self.ping_timeout = ping_timeout
+
+ def read(self, sock, read_callback, check_callback):
+ while self.app.keep_running:
+ r, w, e = select.select(
+ (self.app.sock.sock, ), (), (), self.ping_timeout)
+ if r:
+ if not read_callback():
+ break
+ check_callback()
+
+class SSLDispatcher:
+ """
+ SSLDispatcher
+ """
+ def __init__(self, app, ping_timeout):
+ self.app = app
+ self.ping_timeout = ping_timeout
+
+ def read(self, sock, read_callback, check_callback):
+ while self.app.keep_running:
+ r = self.select()
+ if r:
+ if not read_callback():
+ break
+ check_callback()
+
+ def select(self):
+ sock = self.app.sock.sock
+ if sock.pending():
+ return [sock,]
+
+ r, w, e = select.select((sock, ), (), (), self.ping_timeout)
+ return r
+
+
+class WebSocketApp(object):
+ """
+ Higher level of APIs are provided. The interface is like JavaScript WebSocket object.
+ """
+
+ def __init__(self, url, header=None,
+ on_open=None, on_message=None, on_error=None,
+ on_close=None, on_ping=None, on_pong=None,
+ on_cont_message=None,
+ keep_running=True, get_mask_key=None, cookie=None,
+ subprotocols=None,
+ on_data=None):
+ """
+ WebSocketApp initialization
+
+ Parameters
+ ----------
+ url:
+ websocket url.
+ header: list or dict
+ custom header for websocket handshake.
+ on_open:
+ callable object which is called at opening websocket.
+ this function has one argument. The argument is this class object.
+ on_message:
+ callable object which is called when received data.
+ on_message has 2 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ on_error:
+ callable object which is called when we get error.
+ on_error has 2 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is exception object.
+ on_close:
+ callable object which is called when closed the connection.
+ this function has one argument. The argument is this class object.
+ on_cont_message:
+ callback object which is called when receive continued
+ frame data.
+ on_cont_message has 3 arguments.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ The 3rd argument is continue flag. if 0, the data continue
+ to next frame data
+ on_data:
+ callback object which is called when a message received.
+ This is called before on_message or on_cont_message,
+ and then on_message or on_cont_message is called.
+ on_data has 4 argument.
+ The 1st argument is this class object.
+ The 2nd argument is utf-8 string which we get from the server.
+ The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came.
+ The 4th argument is continue flag. if 0, the data continue
+ keep_running:
+ this parameter is obsolete and ignored.
+ get_mask_key: func
+ a callable to produce new mask keys,
+ see the WebSocket.set_mask_key's docstring for more information
+ cookie: str
+ cookie value.
+ subprotocols:
+ array of available sub protocols. default is None.
+ """
+ self.url = url
+ self.header = header if header is not None else []
+ self.cookie = cookie
+
+ self.on_open = on_open
+ self.on_message = on_message
+ self.on_data = on_data
+ self.on_error = on_error
+ self.on_close = on_close
+ self.on_ping = on_ping
+ self.on_pong = on_pong
+ self.on_cont_message = on_cont_message
+ self.keep_running = False
+ self.get_mask_key = get_mask_key
+ self.sock = None
+ self.last_ping_tm = 0
+ self.last_pong_tm = 0
+ self.subprotocols = subprotocols
+
+ def send(self, data, opcode=ABNF.OPCODE_TEXT):
+ """
+ send message
+
+ Parameters
+ ----------
+ data:
+ Message to send. If you set opcode to OPCODE_TEXT,
+ data must be utf-8 string or unicode.
+ opcode:
+ Operation code of data. default is OPCODE_TEXT.
+ """
+
+ if not self.sock or self.sock.send(data, opcode) == 0:
+ raise WebSocketConnectionClosedException(
+ "Connection is already closed.")
+
+ def close(self, **kwargs):
+ """
+ Close websocket connection.
+ """
+ self.keep_running = False
+ if self.sock:
+ self.sock.close(**kwargs)
+ self.sock = None
+
+ def _send_ping(self, interval, event):
+ while not event.wait(interval):
+ self.last_ping_tm = time.time()
+ if self.sock:
+ try:
+ self.sock.ping()
+ except Exception as ex:
+ _logging.warning("send_ping routine terminated: {}".format(ex))
+ break
+
+ 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):
+ """
+ Run event loop for WebSocket framework.
+
+ This loop is an infinite loop and is alive while websocket is available.
+
+ Parameters
+ ----------
+ sockopt: tuple
+ values for socket.setsockopt.
+ sockopt must be tuple
+ and each element is argument of sock.setsockopt.
+ sslopt: dict
+ optional dict object for ssl socket option.
+ ping_interval: int or float
+ automatically send "ping" command
+ every specified period (in seconds)
+ if set to 0, not send automatically.
+ ping_timeout: int or float
+ timeout (in seconds) if the pong message is not received.
+ http_proxy_host:
+ http proxy host name.
+ http_proxy_port:
+ http proxy port. If not set, set to 80.
+ http_no_proxy:
+ host names, which doesn't use proxy.
+ skip_utf8_validation: bool
+ skip utf8 validation.
+ host: str
+ update host header.
+ origin: str
+ update origin header.
+ dispatcher:
+ customize reading data from socket.
+ suppress_origin: bool
+ suppress outputting origin header.
+
+ Returns
+ -------
+ teardown: bool
+ False if caught KeyboardInterrupt, True if other exception was raised during a loop
+ """
+
+ if ping_timeout is not None and ping_timeout <= 0:
+ ping_timeout = None
+ if ping_timeout and ping_interval and ping_interval <= ping_timeout:
+ raise WebSocketException("Ensure ping_interval > ping_timeout")
+ if not sockopt:
+ sockopt = []
+ if not sslopt:
+ sslopt = {}
+ if self.sock:
+ raise WebSocketException("socket is already opened")
+ thread = None
+ self.keep_running = True
+ self.last_ping_tm = 0
+ self.last_pong_tm = 0
+
+ def teardown(close_frame=None):
+ """
+ Tears down the connection.
+
+ If close_frame is set, we will invoke the on_close handler with the
+ statusCode and reason from there.
+ """
+ if thread and thread.is_alive():
+ event.set()
+ thread.join()
+ self.keep_running = False
+ if self.sock:
+ self.sock.close()
+ close_args = self._get_close_args(
+ close_frame.data if close_frame else None)
+ self._callback(self.on_close, *close_args)
+ self.sock = None
+
+ try:
+ self.sock = WebSocket(
+ self.get_mask_key, sockopt=sockopt, sslopt=sslopt,
+ fire_cont_frame=self.on_cont_message is not None,
+ skip_utf8_validation=skip_utf8_validation,
+ enable_multithread=True if ping_interval else False)
+ self.sock.settimeout(getdefaulttimeout())
+ self.sock.connect(
+ self.url, header=self.header, cookie=self.cookie,
+ http_proxy_host=http_proxy_host,
+ http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy,
+ http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols,
+ host=host, origin=origin, suppress_origin=suppress_origin,
+ proxy_type=proxy_type)
+ if not dispatcher:
+ dispatcher = self.create_dispatcher(ping_timeout)
+
+ self._callback(self.on_open)
+
+ if ping_interval:
+ event = threading.Event()
+ thread = threading.Thread(
+ target=self._send_ping, args=(ping_interval, event))
+ thread.setDaemon(True)
+ thread.start()
+
+ def read():
+ if not self.keep_running:
+ return teardown()
+
+ op_code, frame = self.sock.recv_data_frame(True)
+ if op_code == ABNF.OPCODE_CLOSE:
+ return teardown(frame)
+ elif op_code == ABNF.OPCODE_PING:
+ self._callback(self.on_ping, frame.data)
+ elif op_code == ABNF.OPCODE_PONG:
+ self.last_pong_tm = time.time()
+ self._callback(self.on_pong, frame.data)
+ elif op_code == ABNF.OPCODE_CONT and self.on_cont_message:
+ self._callback(self.on_data, frame.data,
+ frame.opcode, frame.fin)
+ self._callback(self.on_cont_message,
+ frame.data, frame.fin)
+ else:
+ data = frame.data
+ if six.PY3 and op_code == ABNF.OPCODE_TEXT:
+ data = data.decode("utf-8")
+ self._callback(self.on_data, data, frame.opcode, True)
+ self._callback(self.on_message, data)
+
+ return True
+
+ def check():
+ if (ping_timeout):
+ has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout
+ has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0
+ has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout
+
+ if (self.last_ping_tm
+ and has_timeout_expired
+ and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)):
+ raise WebSocketTimeoutException("ping/pong timed out")
+ return True
+
+ dispatcher.read(self.sock.sock, read, check)
+ except (Exception, KeyboardInterrupt, SystemExit) as e:
+ self._callback(self.on_error, e)
+ if isinstance(e, SystemExit):
+ # propagate SystemExit further
+ raise
+ teardown()
+ return not isinstance(e, KeyboardInterrupt)
+
+ def create_dispatcher(self, ping_timeout):
+ timeout = ping_timeout or 10
+ if self.sock.is_ssl():
+ return SSLDispatcher(self, timeout)
+
+ return Dispatcher(self, timeout)
+
+ def _get_close_args(self, data):
+ """
+ _get_close_args extracts the code, reason from the close body
+ if they exists, and if the self.on_close except three arguments
+ """
+ # if the on_close callback is "old", just return empty list
+ if sys.version_info < (3, 0):
+ if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3:
+ return []
+ else:
+ if not self.on_close or len(inspect.getfullargspec(self.on_close).args) != 3:
+ return []
+
+ if data and len(data) >= 2:
+ code = 256 * six.byte2int(data[0:1]) + six.byte2int(data[1:2])
+ reason = data[2:].decode('utf-8')
+ return [code, reason]
+
+ return [None, None]
+
+ def _callback(self, callback, *args):
+ if callback:
+ try:
+ callback(self, *args)
+
+ except Exception as e:
+ _logging.error("error from callback {}: {}".format(callback, e))
+ if _logging.isEnabledForDebug():
+ _, _, tb = sys.exc_info()
+ traceback.print_tb(tb)
diff --git a/resources/lib/websocket/_cookiejar.py b/resources/lib/websocket/_cookiejar.py
new file mode 100644
index 00000000..06474b57
--- /dev/null
+++ b/resources/lib/websocket/_cookiejar.py
@@ -0,0 +1,76 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+try:
+ import Cookie
+except:
+ import http.cookies as Cookie
+
+
+class SimpleCookieJar(object):
+ def __init__(self):
+ self.jar = dict()
+
+ def add(self, set_cookie):
+ if set_cookie:
+ try:
+ simpleCookie = Cookie.SimpleCookie(set_cookie)
+ except:
+ simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore'))
+
+ for k, v in simpleCookie.items():
+ domain = v.get("domain")
+ if domain:
+ if not domain.startswith("."):
+ domain = "." + domain
+ cookie = self.jar.get(domain) if self.jar.get(domain) else Cookie.SimpleCookie()
+ cookie.update(simpleCookie)
+ self.jar[domain.lower()] = cookie
+
+ def set(self, set_cookie):
+ if set_cookie:
+ try:
+ simpleCookie = Cookie.SimpleCookie(set_cookie)
+ except:
+ simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore'))
+
+ for k, v in simpleCookie.items():
+ domain = v.get("domain")
+ if domain:
+ if not domain.startswith("."):
+ domain = "." + domain
+ self.jar[domain.lower()] = simpleCookie
+
+ def get(self, host):
+ if not host:
+ return ""
+
+ cookies = []
+ for domain, simpleCookie in self.jar.items():
+ host = host.lower()
+ if host.endswith(domain) or host == domain[1:]:
+ cookies.append(self.jar.get(domain))
+
+ return "; ".join(filter(None, ["%s=%s" % (k, v.value) for cookie in filter(None, sorted(cookies)) for k, v in
+ sorted(cookie.items())]))
diff --git a/resources/lib/websocket/_core.py b/resources/lib/websocket/_core.py
new file mode 100644
index 00000000..a3e484b4
--- /dev/null
+++ b/resources/lib/websocket/_core.py
@@ -0,0 +1,595 @@
+from __future__ import print_function
+"""
+_core.py
+====================================
+WebSocket Python client
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import socket
+import struct
+import threading
+import time
+
+import six
+
+# websocket modules
+from ._abnf import *
+from ._exceptions import *
+from ._handshake import *
+from ._http import *
+from ._logging import *
+from ._socket import *
+from ._ssl_compat import *
+from ._utils import *
+
+__all__ = ['WebSocket', 'create_connection']
+
+class WebSocket(object):
+ """
+ Low level WebSocket interface.
+
+ This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 `_
+
+ We can connect to the websocket server and send/receive data.
+ The following example is an echo client.
+
+ >>> import websocket
+ >>> ws = websocket.WebSocket()
+ >>> ws.connect("ws://echo.websocket.org")
+ >>> ws.send("Hello, Server")
+ >>> ws.recv()
+ 'Hello, Server'
+ >>> ws.close()
+
+ Parameters
+ ----------
+ get_mask_key: func
+ a callable to produce new mask keys, see the set_mask_key
+ function's docstring for more details
+ sockopt: tuple
+ values for socket.setsockopt.
+ sockopt must be tuple and each element is argument of sock.setsockopt.
+ sslopt: dict
+ optional dict object for ssl socket option.
+ fire_cont_frame: bool
+ fire recv event for each cont frame. default is False
+ enable_multithread: bool
+ if set to True, lock send method.
+ skip_utf8_validation: bool
+ skip utf8 validation.
+ """
+
+ def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
+ fire_cont_frame=False, enable_multithread=False,
+ skip_utf8_validation=False, **_):
+ """
+ Initialize WebSocket object.
+
+ Parameters
+ ----------
+ sslopt: specify ssl certification verification options
+ """
+ self.sock_opt = sock_opt(sockopt, sslopt)
+ self.handshake_response = None
+ self.sock = None
+
+ self.connected = False
+ self.get_mask_key = get_mask_key
+ # These buffer over the build-up of a single frame.
+ self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
+ self.cont_frame = continuous_frame(
+ fire_cont_frame, skip_utf8_validation)
+
+ if enable_multithread:
+ self.lock = threading.Lock()
+ self.readlock = threading.Lock()
+ else:
+ self.lock = NoLock()
+ self.readlock = NoLock()
+
+ def __iter__(self):
+ """
+ Allow iteration over websocket, implying sequential `recv` executions.
+ """
+ while True:
+ yield self.recv()
+
+ def __next__(self):
+ return self.recv()
+
+ def next(self):
+ return self.__next__()
+
+ def fileno(self):
+ return self.sock.fileno()
+
+ def set_mask_key(self, func):
+ """
+ Set function to create mask key. You can customize mask key generator.
+ Mainly, this is for testing purpose.
+
+ Parameters
+ ----------
+ func: func
+ callable object. the func takes 1 argument as integer.
+ The argument means length of mask key.
+ This func must return string(byte array),
+ which length is argument specified.
+ """
+ self.get_mask_key = func
+
+ def gettimeout(self):
+ """
+ Get the websocket timeout (in seconds) as an int or float
+
+ Returns
+ ----------
+ timeout: int or float
+ returns timeout value (in seconds). This value could be either float/integer.
+ """
+ return self.sock_opt.timeout
+
+ def settimeout(self, timeout):
+ """
+ Set the timeout to the websocket.
+
+ Parameters
+ ----------
+ timeout: int or float
+ timeout time (in seconds). This value could be either float/integer.
+ """
+ self.sock_opt.timeout = timeout
+ if self.sock:
+ self.sock.settimeout(timeout)
+
+ timeout = property(gettimeout, settimeout)
+
+ def getsubprotocol(self):
+ """
+ Get subprotocol
+ """
+ if self.handshake_response:
+ return self.handshake_response.subprotocol
+ else:
+ return None
+
+ subprotocol = property(getsubprotocol)
+
+ def getstatus(self):
+ """
+ Get handshake status
+ """
+ if self.handshake_response:
+ return self.handshake_response.status
+ else:
+ return None
+
+ status = property(getstatus)
+
+ def getheaders(self):
+ """
+ Get handshake response header
+ """
+ if self.handshake_response:
+ return self.handshake_response.headers
+ else:
+ return None
+
+ def is_ssl(self):
+ return isinstance(self.sock, ssl.SSLSocket)
+
+ headers = property(getheaders)
+
+ def connect(self, url, **options):
+ """
+ Connect to url. url is websocket url scheme.
+ ie. ws://host:port/resource
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> ws = WebSocket()
+ >>> ws.connect("ws://echo.websocket.org/",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+ timeout:
+ socket timeout time. This value is an integer or float.
+ if you set None for this value, it means "use default_timeout value"
+
+ Parameters
+ ----------
+ options:
+ - header: list or dict
+ custom http header list or dict.
+ - cookie: str
+ cookie value.
+ - origin: str
+ custom origin url.
+ - suppress_origin: bool
+ suppress outputting origin header.
+ - host: str
+ custom host header string.
+ - http_proxy_host:
+ http proxy host name.
+ - http_proxy_port:
+ http proxy port. If not set, set to 80.
+ - http_no_proxy:
+ host names, which doesn't use proxy.
+ - http_proxy_auth:
+ http proxy auth information. tuple of username and password. default is None
+ - redirect_limit:
+ number of redirects to follow.
+ - subprotocols:
+ array of available sub protocols. default is None.
+ - socket:
+ pre-initialized stream socket.
+ """
+ self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
+ self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
+ options.pop('socket', None))
+
+ try:
+ self.handshake_response = handshake(self.sock, *addrs, **options)
+ for attempt in range(options.pop('redirect_limit', 3)):
+ if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES:
+ url = self.handshake_response.headers['location']
+ self.sock.close()
+ self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
+ options.pop('socket', None))
+ self.handshake_response = handshake(self.sock, *addrs, **options)
+ self.connected = True
+ except:
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ raise
+
+ def send(self, payload, opcode=ABNF.OPCODE_TEXT):
+ """
+ Send the data as string.
+
+ Parameters
+ ----------
+ payload:
+ Payload must be utf-8 string or unicode,
+ if the opcode is OPCODE_TEXT.
+ Otherwise, it must be string(byte array)
+ opcode:
+ operation code to send. Please see OPCODE_XXX.
+ """
+
+ frame = ABNF.create_frame(payload, opcode)
+ return self.send_frame(frame)
+
+ def send_frame(self, frame):
+ """
+ Send the data frame.
+
+ >>> ws = create_connection("ws://echo.websocket.org/")
+ >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
+ >>> ws.send_frame(frame)
+ >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
+ >>> ws.send_frame(frame)
+ >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
+ >>> ws.send_frame(frame)
+
+ Parameters
+ ----------
+ frame:
+ frame data created by ABNF.create_frame
+ """
+ if self.get_mask_key:
+ frame.get_mask_key = self.get_mask_key
+ data = frame.format()
+ length = len(data)
+ if (isEnabledForTrace()):
+ trace("send: " + repr(data))
+
+ with self.lock:
+ while data:
+ l = self._send(data)
+ data = data[l:]
+
+ return length
+
+ def send_binary(self, payload):
+ return self.send(payload, ABNF.OPCODE_BINARY)
+
+ def ping(self, payload=""):
+ """
+ Send ping data.
+
+ Parameters
+ ----------
+ payload:
+ data payload to send server.
+ """
+ if isinstance(payload, six.text_type):
+ payload = payload.encode("utf-8")
+ self.send(payload, ABNF.OPCODE_PING)
+
+ def pong(self, payload=""):
+ """
+ Send pong data.
+
+ Parameters
+ ----------
+ payload:
+ data payload to send server.
+ """
+ if isinstance(payload, six.text_type):
+ payload = payload.encode("utf-8")
+ self.send(payload, ABNF.OPCODE_PONG)
+
+ def recv(self):
+ """
+ Receive string data(byte array) from the server.
+
+ Returns
+ ----------
+ data: string (byte array) value.
+ """
+ with self.readlock:
+ opcode, data = self.recv_data()
+ if six.PY3 and opcode == ABNF.OPCODE_TEXT:
+ return data.decode("utf-8")
+ elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY:
+ return data
+ else:
+ return ''
+
+ def recv_data(self, control_frame=False):
+ """
+ Receive data with operation code.
+
+ Parameters
+ ----------
+ control_frame: bool
+ a boolean flag indicating whether to return control frame
+ data, defaults to False
+
+ Returns
+ -------
+ opcode, frame.data: tuple
+ tuple of operation code and string(byte array) value.
+ """
+ opcode, frame = self.recv_data_frame(control_frame)
+ return opcode, frame.data
+
+ def recv_data_frame(self, control_frame=False):
+ """
+ Receive data with operation code.
+
+ Parameters
+ ----------
+ control_frame: bool
+ a boolean flag indicating whether to return control frame
+ data, defaults to False
+
+ Returns
+ -------
+ frame.opcode, frame: tuple
+ tuple of operation code and string(byte array) value.
+ """
+ while True:
+ frame = self.recv_frame()
+ if not frame:
+ # handle error:
+ # 'NoneType' object has no attribute 'opcode'
+ raise WebSocketProtocolException(
+ "Not a valid frame %s" % frame)
+ elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
+ self.cont_frame.validate(frame)
+ self.cont_frame.add(frame)
+
+ if self.cont_frame.is_fire(frame):
+ return self.cont_frame.extract(frame)
+
+ elif frame.opcode == ABNF.OPCODE_CLOSE:
+ self.send_close()
+ return frame.opcode, frame
+ elif frame.opcode == ABNF.OPCODE_PING:
+ if len(frame.data) < 126:
+ self.pong(frame.data)
+ else:
+ raise WebSocketProtocolException(
+ "Ping message is too long")
+ if control_frame:
+ return frame.opcode, frame
+ elif frame.opcode == ABNF.OPCODE_PONG:
+ if control_frame:
+ return frame.opcode, frame
+
+ def recv_frame(self):
+ """
+ Receive data as frame from server.
+
+ Returns
+ -------
+ self.frame_buffer.recv_frame(): ABNF frame object
+ """
+ return self.frame_buffer.recv_frame()
+
+ def send_close(self, status=STATUS_NORMAL, reason=six.b("")):
+ """
+ Send close data to the server.
+
+ Parameters
+ ----------
+ status:
+ status code to send. see STATUS_XXX.
+ reason: str or bytes
+ the reason to close. This must be string or bytes.
+ """
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+ self.connected = False
+ self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+
+ def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3):
+ """
+ Close Websocket object
+
+ Parameters
+ ----------
+ status:
+ status code to send. see STATUS_XXX.
+ reason:
+ the reason to close. This must be string.
+ timeout: int or float
+ timeout until receive a close frame.
+ If None, it will wait forever until receive a close frame.
+ """
+ if self.connected:
+ if status < 0 or status >= ABNF.LENGTH_16:
+ raise ValueError("code is invalid range")
+
+ try:
+ self.connected = False
+ self.send(struct.pack('!H', status) +
+ reason, ABNF.OPCODE_CLOSE)
+ sock_timeout = self.sock.gettimeout()
+ self.sock.settimeout(timeout)
+ start_time = time.time()
+ while timeout is None or time.time() - start_time < timeout:
+ try:
+ frame = self.recv_frame()
+ if frame.opcode != ABNF.OPCODE_CLOSE:
+ continue
+ if isEnabledForError():
+ recv_status = struct.unpack("!H", frame.data[0:2])[0]
+ if recv_status >= 3000 and recv_status <= 4999:
+ debug("close status: " + repr(recv_status))
+ elif recv_status != STATUS_NORMAL:
+ error("close status: " + repr(recv_status))
+ break
+ except:
+ break
+ self.sock.settimeout(sock_timeout)
+ self.sock.shutdown(socket.SHUT_RDWR)
+ except:
+ pass
+
+ self.shutdown()
+
+ def abort(self):
+ """
+ Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
+ """
+ if self.connected:
+ self.sock.shutdown(socket.SHUT_RDWR)
+
+ def shutdown(self):
+ """
+ close socket, immediately.
+ """
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ self.connected = False
+
+ def _send(self, data):
+ return send(self.sock, data)
+
+ def _recv(self, bufsize):
+ try:
+ return recv(self.sock, bufsize)
+ except WebSocketConnectionClosedException:
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+ self.connected = False
+ raise
+
+
+def create_connection(url, timeout=None, class_=WebSocket, **options):
+ """
+ Connect to url and return websocket object.
+
+ Connect to url and return the WebSocket object.
+ Passing optional timeout parameter will set the timeout on the socket.
+ If no timeout is supplied,
+ the global default timeout setting returned by getdefaulttimeout() is used.
+ You can customize using 'options'.
+ If you set "header" list object, you can set your own custom header.
+
+ >>> conn = create_connection("ws://echo.websocket.org/",
+ ... header=["User-Agent: MyProgram",
+ ... "x-custom: header"])
+
+ Parameters
+ ----------
+ timeout: int or float
+ socket timeout time. This value could be either float/integer.
+ if you set None for this value,
+ it means "use default_timeout value"
+ class_:
+ class to instantiate when creating the connection. It has to implement
+ settimeout and connect. It's __init__ should be compatible with
+ WebSocket.__init__, i.e. accept all of it's kwargs.
+ options:
+ - header: list or dict
+ custom http header list or dict.
+ - cookie: str
+ cookie value.
+ - origin: str
+ custom origin url.
+ - suppress_origin: bool
+ suppress outputting origin header.
+ - host:
+ custom host header string.
+ - http_proxy_host:
+ http proxy host name.
+ - http_proxy_port:
+ http proxy port. If not set, set to 80.
+ - http_no_proxy:
+ host names, which doesn't use proxy.
+ - http_proxy_auth:
+ http proxy auth information. tuple of username and password. default is None
+ - enable_multithread: bool
+ enable lock for multithread.
+ - redirect_limit:
+ number of redirects to follow.
+ - sockopt:
+ socket options
+ - sslopt:
+ ssl option
+ - subprotocols:
+ array of available sub protocols. default is None.
+ - skip_utf8_validation: bool
+ skip utf8 validation.
+ - socket:
+ pre-initialized stream socket.
+ """
+ sockopt = options.pop("sockopt", [])
+ sslopt = options.pop("sslopt", {})
+ fire_cont_frame = options.pop("fire_cont_frame", False)
+ enable_multithread = options.pop("enable_multithread", False)
+ skip_utf8_validation = options.pop("skip_utf8_validation", False)
+ websock = class_(sockopt=sockopt, sslopt=sslopt,
+ fire_cont_frame=fire_cont_frame,
+ enable_multithread=enable_multithread,
+ skip_utf8_validation=skip_utf8_validation, **options)
+ websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
+ websock.connect(url, **options)
+ return websock
diff --git a/resources/lib/websocket/_exceptions.py b/resources/lib/websocket/_exceptions.py
new file mode 100644
index 00000000..be56b6e6
--- /dev/null
+++ b/resources/lib/websocket/_exceptions.py
@@ -0,0 +1,85 @@
+"""
+Define WebSocket exceptions
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+
+class WebSocketException(Exception):
+ """
+ WebSocket exception class.
+ """
+ pass
+
+
+class WebSocketProtocolException(WebSocketException):
+ """
+ If the WebSocket protocol is invalid, this exception will be raised.
+ """
+ pass
+
+
+class WebSocketPayloadException(WebSocketException):
+ """
+ If the WebSocket payload is invalid, this exception will be raised.
+ """
+ pass
+
+
+class WebSocketConnectionClosedException(WebSocketException):
+ """
+ If remote host closed the connection or some network error happened,
+ this exception will be raised.
+ """
+ pass
+
+
+class WebSocketTimeoutException(WebSocketException):
+ """
+ WebSocketTimeoutException will be raised at socket timeout during read/write data.
+ """
+ pass
+
+
+class WebSocketProxyException(WebSocketException):
+ """
+ WebSocketProxyException will be raised when proxy error occurred.
+ """
+ pass
+
+
+class WebSocketBadStatusException(WebSocketException):
+ """
+ WebSocketBadStatusException will be raised when we get bad handshake status code.
+ """
+
+ def __init__(self, message, status_code, status_message=None, resp_headers=None):
+ msg = message % (status_code, status_message)
+ super(WebSocketBadStatusException, self).__init__(msg)
+ self.status_code = status_code
+ self.resp_headers = resp_headers
+
+
+class WebSocketAddressException(WebSocketException):
+ """
+ If the websocket address info cannot be found, this exception will be raised.
+ """
+ pass
diff --git a/resources/lib/websocket/_handshake.py b/resources/lib/websocket/_handshake.py
new file mode 100644
index 00000000..6c93f7cf
--- /dev/null
+++ b/resources/lib/websocket/_handshake.py
@@ -0,0 +1,211 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import hashlib
+import hmac
+import os
+
+import six
+
+from ._cookiejar import SimpleCookieJar
+from ._exceptions import *
+from ._http import *
+from ._logging import *
+from ._socket import *
+
+if hasattr(six, 'PY3') and six.PY3:
+ from base64 import encodebytes as base64encode
+else:
+ from base64 import encodestring as base64encode
+
+if hasattr(six, 'PY3') and six.PY3:
+ if hasattr(six, 'PY34') and six.PY34:
+ from http import client as HTTPStatus
+ else:
+ from http import HTTPStatus
+else:
+ import httplib as HTTPStatus
+
+__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"]
+
+if hasattr(hmac, "compare_digest"):
+ compare_digest = hmac.compare_digest
+else:
+ def compare_digest(s1, s2):
+ return s1 == s2
+
+# websocket supported version.
+VERSION = 13
+
+SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER,)
+SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,)
+
+CookieJar = SimpleCookieJar()
+
+
+class handshake_response(object):
+
+ def __init__(self, status, headers, subprotocol):
+ self.status = status
+ self.headers = headers
+ self.subprotocol = subprotocol
+ CookieJar.add(headers.get("set-cookie"))
+
+
+def handshake(sock, hostname, port, resource, **options):
+ headers, key = _get_handshake_headers(resource, hostname, port, options)
+
+ header_str = "\r\n".join(headers)
+ send(sock, header_str)
+ dump("request header", header_str)
+
+ status, resp = _get_resp_headers(sock)
+ if status in SUPPORTED_REDIRECT_STATUSES:
+ return handshake_response(status, resp, None)
+ success, subproto = _validate(resp, key, options.get("subprotocols"))
+ if not success:
+ raise WebSocketException("Invalid WebSocket Header")
+
+ return handshake_response(status, resp, subproto)
+
+
+def _pack_hostname(hostname):
+ # IPv6 address
+ if ':' in hostname:
+ return '[' + hostname + ']'
+
+ return hostname
+
+def _get_handshake_headers(resource, host, port, options):
+ headers = [
+ "GET %s HTTP/1.1" % resource,
+ "Upgrade: websocket"
+ ]
+ if port == 80 or port == 443:
+ hostport = _pack_hostname(host)
+ else:
+ hostport = "%s:%d" % (_pack_hostname(host), port)
+ if "host" in options and options["host"] is not None:
+ headers.append("Host: %s" % options["host"])
+ else:
+ headers.append("Host: %s" % hostport)
+
+ if "suppress_origin" not in options or not options["suppress_origin"]:
+ if "origin" in options and options["origin"] is not None:
+ headers.append("Origin: %s" % options["origin"])
+ else:
+ headers.append("Origin: http://%s" % hostport)
+
+ key = _create_sec_websocket_key()
+
+ # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified
+ if not 'header' in options or 'Sec-WebSocket-Key' not in options['header']:
+ key = _create_sec_websocket_key()
+ headers.append("Sec-WebSocket-Key: %s" % key)
+ else:
+ key = options['header']['Sec-WebSocket-Key']
+
+ if not 'header' in options or 'Sec-WebSocket-Version' not in options['header']:
+ headers.append("Sec-WebSocket-Version: %s" % VERSION)
+
+ if not 'connection' in options or options['connection'] is None:
+ headers.append('Connection: Upgrade')
+ else:
+ headers.append(options['connection'])
+
+ subprotocols = options.get("subprotocols")
+ if subprotocols:
+ headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols))
+
+ if "header" in options:
+ header = options["header"]
+ if isinstance(header, dict):
+ header = [
+ ": ".join([k, v])
+ for k, v in header.items()
+ if v is not None
+ ]
+ headers.extend(header)
+
+ server_cookie = CookieJar.get(host)
+ client_cookie = options.get("cookie", None)
+
+ cookie = "; ".join(filter(None, [server_cookie, client_cookie]))
+
+ if cookie:
+ headers.append("Cookie: %s" % cookie)
+
+ headers.append("")
+ headers.append("")
+
+ return headers, key
+
+
+def _get_resp_headers(sock, success_statuses=SUCCESS_STATUSES):
+ status, resp_headers, status_message = read_headers(sock)
+ if status not in success_statuses:
+ raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
+ return status, resp_headers
+
+
+_HEADERS_TO_CHECK = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+}
+
+
+def _validate(headers, key, subprotocols):
+ subproto = None
+ for k, v in _HEADERS_TO_CHECK.items():
+ r = headers.get(k, None)
+ if not r:
+ return False, None
+ r = r.lower()
+ if v != r:
+ return False, None
+
+ if subprotocols:
+ subproto = headers.get("sec-websocket-protocol", None)
+ if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]:
+ error("Invalid subprotocol: " + str(subprotocols))
+ return False, None
+ subproto = subproto.lower()
+
+ result = headers.get("sec-websocket-accept", None)
+ if not result:
+ return False, None
+ result = result.lower()
+
+ if isinstance(result, six.text_type):
+ result = result.encode('utf-8')
+
+ value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8')
+ hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()
+ success = compare_digest(hashed, result)
+
+ if success:
+ return True, subproto
+ else:
+ return False, None
+
+
+def _create_sec_websocket_key():
+ randomness = os.urandom(16)
+ return base64encode(randomness).decode('utf-8').strip()
diff --git a/resources/lib/websocket/_http.py b/resources/lib/websocket/_http.py
new file mode 100644
index 00000000..557f9689
--- /dev/null
+++ b/resources/lib/websocket/_http.py
@@ -0,0 +1,329 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import errno
+import os
+import socket
+import sys
+
+import six
+
+from ._exceptions import *
+from ._logging import *
+from ._socket import*
+from ._ssl_compat import *
+from ._url import *
+
+if six.PY3:
+ from base64 import encodebytes as base64encode
+else:
+ from base64 import encodestring as base64encode
+
+__all__ = ["proxy_info", "connect", "read_headers"]
+
+try:
+ import socks
+ ProxyConnectionError = socks.ProxyConnectionError
+ HAS_PYSOCKS = True
+except:
+ class ProxyConnectionError(BaseException):
+ pass
+ HAS_PYSOCKS = False
+
+class proxy_info(object):
+
+ def __init__(self, **options):
+ self.type = options.get("proxy_type") or "http"
+ if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']):
+ raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'")
+ self.host = options.get("http_proxy_host", None)
+ if self.host:
+ self.port = options.get("http_proxy_port", 0)
+ self.auth = options.get("http_proxy_auth", None)
+ self.no_proxy = options.get("http_no_proxy", None)
+ else:
+ self.port = 0
+ self.auth = None
+ self.no_proxy = None
+
+
+def _open_proxied_socket(url, options, proxy):
+ hostname, port, resource, is_secure = parse_url(url)
+
+ if not HAS_PYSOCKS:
+ raise WebSocketException("PySocks module not found.")
+
+ ptype = socks.SOCKS5
+ rdns = False
+ if proxy.type == "socks4":
+ ptype = socks.SOCKS4
+ if proxy.type == "http":
+ ptype = socks.HTTP
+ if proxy.type[-1] == "h":
+ rdns = True
+
+ sock = socks.create_connection(
+ (hostname, port),
+ proxy_type = ptype,
+ proxy_addr = proxy.host,
+ proxy_port = proxy.port,
+ proxy_rdns = rdns,
+ proxy_username = proxy.auth[0] if proxy.auth else None,
+ proxy_password = proxy.auth[1] if proxy.auth else None,
+ timeout = options.timeout,
+ socket_options = DEFAULT_SOCKET_OPTION + options.sockopt
+ )
+
+ if is_secure:
+ if HAVE_SSL:
+ sock = _ssl_socket(sock, options.sslopt, hostname)
+ else:
+ raise WebSocketException("SSL not available.")
+
+ return sock, (hostname, port, resource)
+
+
+def connect(url, options, proxy, socket):
+ if proxy.host and not socket and not (proxy.type == 'http'):
+ return _open_proxied_socket(url, options, proxy)
+
+ hostname, port, resource, is_secure = parse_url(url)
+
+ if socket:
+ return socket, (hostname, port, resource)
+
+ addrinfo_list, need_tunnel, auth = _get_addrinfo_list(
+ hostname, port, is_secure, proxy)
+ if not addrinfo_list:
+ raise WebSocketException(
+ "Host not found.: " + hostname + ":" + str(port))
+
+ sock = None
+ try:
+ sock = _open_socket(addrinfo_list, options.sockopt, options.timeout)
+ if need_tunnel:
+ sock = _tunnel(sock, hostname, port, auth)
+
+ if is_secure:
+ if HAVE_SSL:
+ sock = _ssl_socket(sock, options.sslopt, hostname)
+ else:
+ raise WebSocketException("SSL not available.")
+
+ return sock, (hostname, port, resource)
+ except:
+ if sock:
+ sock.close()
+ raise
+
+
+def _get_addrinfo_list(hostname, port, is_secure, proxy):
+ phost, pport, pauth = get_proxy_info(
+ hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy)
+ try:
+ # when running on windows 10, getaddrinfo without socktype returns a socktype 0.
+ # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0`
+ # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM.
+ if not phost:
+ addrinfo_list = socket.getaddrinfo(
+ hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+ return addrinfo_list, False, None
+ else:
+ pport = pport and pport or 80
+ # when running on windows 10, the getaddrinfo used above
+ # returns a socktype 0. This generates an error exception:
+ # _on_error: exception Socket type must be stream or datagram, not 0
+ # Force the socket type to SOCK_STREAM
+ addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP)
+ return addrinfo_list, True, pauth
+ except socket.gaierror as e:
+ raise WebSocketAddressException(e)
+
+
+def _open_socket(addrinfo_list, sockopt, timeout):
+ err = None
+ for addrinfo in addrinfo_list:
+ family, socktype, proto = addrinfo[:3]
+ sock = socket.socket(family, socktype, proto)
+ sock.settimeout(timeout)
+ for opts in DEFAULT_SOCKET_OPTION:
+ sock.setsockopt(*opts)
+ for opts in sockopt:
+ sock.setsockopt(*opts)
+
+ address = addrinfo[4]
+ err = None
+ while not err:
+ try:
+ sock.connect(address)
+ except ProxyConnectionError as error:
+ err = WebSocketProxyException(str(error))
+ err.remote_ip = str(address[0])
+ continue
+ except socket.error as error:
+ error.remote_ip = str(address[0])
+ try:
+ eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED)
+ except:
+ eConnRefused = (errno.ECONNREFUSED, )
+ if error.errno == errno.EINTR:
+ continue
+ elif error.errno in eConnRefused:
+ err = error
+ continue
+ else:
+ raise error
+ else:
+ break
+ else:
+ continue
+ break
+ else:
+ if err:
+ raise err
+
+ return sock
+
+
+def _can_use_sni():
+ return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2)
+
+
+def _wrap_sni_socket(sock, sslopt, hostname, check_hostname):
+ context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23))
+
+ if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE:
+ cafile = sslopt.get('ca_certs', None)
+ capath = sslopt.get('ca_cert_path', None)
+ if cafile or capath:
+ context.load_verify_locations(cafile=cafile, capath=capath)
+ elif hasattr(context, 'load_default_certs'):
+ context.load_default_certs(ssl.Purpose.SERVER_AUTH)
+ if sslopt.get('certfile', None):
+ context.load_cert_chain(
+ sslopt['certfile'],
+ sslopt.get('keyfile', None),
+ sslopt.get('password', None),
+ )
+ # see
+ # https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153
+ context.verify_mode = sslopt['cert_reqs']
+ if HAVE_CONTEXT_CHECK_HOSTNAME:
+ context.check_hostname = check_hostname
+ if 'ciphers' in sslopt:
+ context.set_ciphers(sslopt['ciphers'])
+ if 'cert_chain' in sslopt:
+ certfile, keyfile, password = sslopt['cert_chain']
+ context.load_cert_chain(certfile, keyfile, password)
+ if 'ecdh_curve' in sslopt:
+ context.set_ecdh_curve(sslopt['ecdh_curve'])
+
+ return context.wrap_socket(
+ sock,
+ do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True),
+ suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True),
+ server_hostname=hostname,
+ )
+
+
+def _ssl_socket(sock, user_sslopt, hostname):
+ sslopt = dict(cert_reqs=ssl.CERT_REQUIRED)
+ sslopt.update(user_sslopt)
+
+ certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE')
+ if certPath and os.path.isfile(certPath) \
+ and user_sslopt.get('ca_certs', None) is None \
+ and user_sslopt.get('ca_cert', None) is None:
+ sslopt['ca_certs'] = certPath
+ elif certPath and os.path.isdir(certPath) \
+ and user_sslopt.get('ca_cert_path', None) is None:
+ sslopt['ca_cert_path'] = certPath
+
+ check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop(
+ 'check_hostname', True)
+
+ if _can_use_sni():
+ sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname)
+ else:
+ sslopt.pop('check_hostname', True)
+ sock = ssl.wrap_socket(sock, **sslopt)
+
+ if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname:
+ match_hostname(sock.getpeercert(), hostname)
+
+ return sock
+
+
+def _tunnel(sock, host, port, auth):
+ debug("Connecting proxy...")
+ connect_header = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port)
+ # TODO: support digest auth.
+ if auth and auth[0]:
+ auth_str = auth[0]
+ if auth[1]:
+ auth_str += ":" + auth[1]
+ encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '')
+ connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str
+ connect_header += "\r\n"
+ dump("request header", connect_header)
+
+ send(sock, connect_header)
+
+ try:
+ status, resp_headers, status_message = read_headers(sock)
+ except Exception as e:
+ raise WebSocketProxyException(str(e))
+
+ if status != 200:
+ raise WebSocketProxyException(
+ "failed CONNECT via proxy status: %r" % status)
+
+ return sock
+
+
+def read_headers(sock):
+ status = None
+ status_message = None
+ headers = {}
+ trace("--- response header ---")
+
+ while True:
+ line = recv_line(sock)
+ line = line.decode('utf-8').strip()
+ if not line:
+ break
+ trace(line)
+ if not status:
+
+ status_info = line.split(" ", 2)
+ status = int(status_info[1])
+ if len(status_info) > 2:
+ status_message = status_info[2]
+ else:
+ kv = line.split(":", 1)
+ if len(kv) == 2:
+ key, value = kv
+ headers[key.lower()] = value.strip()
+ else:
+ raise WebSocketException("Invalid header")
+
+ trace("-----------------------")
+
+ return status, headers, status_message
diff --git a/resources/lib/websocket/_logging.py b/resources/lib/websocket/_logging.py
new file mode 100644
index 00000000..562af14d
--- /dev/null
+++ b/resources/lib/websocket/_logging.py
@@ -0,0 +1,90 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import logging
+
+_logger = logging.getLogger('websocket')
+try:
+ from logging import NullHandler
+except ImportError:
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+
+_logger.addHandler(NullHandler())
+
+_traceEnabled = False
+
+__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace",
+ "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"]
+
+
+def enableTrace(traceable, handler = logging.StreamHandler()):
+ """
+ Turn on/off the traceability.
+
+ Parameters
+ ----------
+ traceable: bool
+ If set to True, traceability is enabled.
+ """
+ global _traceEnabled
+ _traceEnabled = traceable
+ if traceable:
+ _logger.addHandler(handler)
+ _logger.setLevel(logging.DEBUG)
+
+def dump(title, message):
+ if _traceEnabled:
+ _logger.debug("--- " + title + " ---")
+ _logger.debug(message)
+ _logger.debug("-----------------------")
+
+
+def error(msg):
+ _logger.error(msg)
+
+
+def warning(msg):
+ _logger.warning(msg)
+
+
+def debug(msg):
+ _logger.debug(msg)
+
+
+def trace(msg):
+ if _traceEnabled:
+ _logger.debug(msg)
+
+
+def isEnabledForError():
+ return _logger.isEnabledFor(logging.ERROR)
+
+
+def isEnabledForDebug():
+ return _logger.isEnabledFor(logging.DEBUG)
+
+def isEnabledForTrace():
+ return _traceEnabled
diff --git a/resources/lib/websocket/_socket.py b/resources/lib/websocket/_socket.py
new file mode 100644
index 00000000..f4f4ed9e
--- /dev/null
+++ b/resources/lib/websocket/_socket.py
@@ -0,0 +1,177 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import errno
+import select
+import socket
+
+import six
+import sys
+
+from ._exceptions import *
+from ._ssl_compat import *
+from ._utils import *
+
+DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)]
+if hasattr(socket, "SO_KEEPALIVE"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
+if hasattr(socket, "TCP_KEEPIDLE"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30))
+if hasattr(socket, "TCP_KEEPINTVL"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10))
+if hasattr(socket, "TCP_KEEPCNT"):
+ DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3))
+
+_default_timeout = None
+
+__all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout",
+ "recv", "recv_line", "send"]
+
+
+class sock_opt(object):
+
+ def __init__(self, sockopt, sslopt):
+ if sockopt is None:
+ sockopt = []
+ if sslopt is None:
+ sslopt = {}
+ self.sockopt = sockopt
+ self.sslopt = sslopt
+ self.timeout = None
+
+
+def setdefaulttimeout(timeout):
+ """
+ Set the global timeout setting to connect.
+
+ Parameters
+ ----------
+ timeout: int or float
+ default socket timeout time (in seconds)
+ """
+ global _default_timeout
+ _default_timeout = timeout
+
+
+def getdefaulttimeout():
+ """
+ Get default timeout
+
+ Returns
+ ----------
+ _default_timeout: int or float
+ Return the global timeout setting (in seconds) to connect.
+ """
+ return _default_timeout
+
+
+def recv(sock, bufsize):
+ if not sock:
+ raise WebSocketConnectionClosedException("socket is already closed.")
+
+ def _recv():
+ try:
+ return sock.recv(bufsize)
+ except SSLWantReadError:
+ pass
+ except socket.error as exc:
+ error_code = extract_error_code(exc)
+ if error_code is None:
+ raise
+ if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
+ raise
+
+ r, w, e = select.select((sock, ), (), (), sock.gettimeout())
+ if r:
+ return sock.recv(bufsize)
+
+ try:
+ if sock.gettimeout() == 0:
+ bytes_ = sock.recv(bufsize)
+ else:
+ bytes_ = _recv()
+ except socket.timeout as e:
+ message = extract_err_message(e)
+ raise WebSocketTimeoutException(message)
+ except SSLError as e:
+ message = extract_err_message(e)
+ if isinstance(message, str) and 'timed out' in message:
+ raise WebSocketTimeoutException(message)
+ else:
+ raise
+
+ if not bytes_:
+ raise WebSocketConnectionClosedException(
+ "Connection is already closed.")
+
+ return bytes_
+
+
+def recv_line(sock):
+ line = []
+ while True:
+ c = recv(sock, 1)
+ line.append(c)
+ if c == six.b("\n"):
+ break
+ return six.b("").join(line)
+
+
+def send(sock, data):
+ if isinstance(data, six.text_type):
+ data = data.encode('utf-8')
+
+ if not sock:
+ raise WebSocketConnectionClosedException("socket is already closed.")
+
+ def _send():
+ try:
+ return sock.send(data)
+ except SSLWantWriteError:
+ pass
+ except socket.error as exc:
+ error_code = extract_error_code(exc)
+ if error_code is None:
+ raise
+ if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK:
+ raise
+
+ r, w, e = select.select((), (sock, ), (), sock.gettimeout())
+ if w:
+ return sock.send(data)
+
+ try:
+ if sock.gettimeout() == 0:
+ return sock.send(data)
+ else:
+ return _send()
+ except socket.timeout as e:
+ message = extract_err_message(e)
+ raise WebSocketTimeoutException(message)
+ except Exception as e:
+ message = extract_err_message(e)
+ if isinstance(message, str) and "timed out" in message:
+ raise WebSocketTimeoutException(message)
+ else:
+ raise
diff --git a/resources/lib/websocket/_ssl_compat.py b/resources/lib/websocket/_ssl_compat.py
new file mode 100644
index 00000000..b8df0b39
--- /dev/null
+++ b/resources/lib/websocket/_ssl_compat.py
@@ -0,0 +1,53 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+__all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"]
+
+try:
+ import ssl
+ from ssl import SSLError
+ from ssl import SSLWantReadError
+ from ssl import SSLWantWriteError
+ if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'):
+ HAVE_CONTEXT_CHECK_HOSTNAME = True
+ else:
+ HAVE_CONTEXT_CHECK_HOSTNAME = False
+ if hasattr(ssl, "match_hostname"):
+ from ssl import match_hostname
+ else:
+ from backports.ssl_match_hostname import match_hostname
+ __all__.append("match_hostname")
+ __all__.append("HAVE_CONTEXT_CHECK_HOSTNAME")
+
+ HAVE_SSL = True
+except ImportError:
+ # dummy class of SSLError for ssl none-support environment.
+ class SSLError(Exception):
+ pass
+
+ class SSLWantReadError(Exception):
+ pass
+
+ class SSLWantWriteError(Exception):
+ pass
+
+ ssl = lambda: None
+
+ HAVE_SSL = False
diff --git a/resources/lib/websocket/_url.py b/resources/lib/websocket/_url.py
new file mode 100644
index 00000000..11c713af
--- /dev/null
+++ b/resources/lib/websocket/_url.py
@@ -0,0 +1,172 @@
+"""
+
+"""
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+
+import os
+import socket
+import struct
+
+from six.moves.urllib.parse import urlparse
+
+
+__all__ = ["parse_url", "get_proxy_info"]
+
+
+def parse_url(url):
+ """
+ parse url and the result is tuple of
+ (hostname, port, resource path and the flag of secure mode)
+
+ Parameters
+ ----------
+ url: str
+ url string.
+ """
+ if ":" not in url:
+ raise ValueError("url is invalid")
+
+ scheme, url = url.split(":", 1)
+
+ parsed = urlparse(url, scheme="ws")
+ if parsed.hostname:
+ hostname = parsed.hostname
+ else:
+ raise ValueError("hostname is invalid")
+ port = 0
+ if parsed.port:
+ port = parsed.port
+
+ is_secure = False
+ if scheme == "ws":
+ if not port:
+ port = 80
+ elif scheme == "wss":
+ is_secure = True
+ if not port:
+ port = 443
+ else:
+ raise ValueError("scheme %s is invalid" % scheme)
+
+ if parsed.path:
+ resource = parsed.path
+ else:
+ resource = "/"
+
+ if parsed.query:
+ resource += "?" + parsed.query
+
+ return hostname, port, resource, is_secure
+
+
+DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"]
+
+
+def _is_ip_address(addr):
+ try:
+ socket.inet_aton(addr)
+ except socket.error:
+ return False
+ else:
+ return True
+
+
+def _is_subnet_address(hostname):
+ try:
+ addr, netmask = hostname.split("/")
+ return _is_ip_address(addr) and 0 <= int(netmask) < 32
+ except ValueError:
+ return False
+
+
+def _is_address_in_network(ip, net):
+ ipaddr = struct.unpack('I', socket.inet_aton(ip))[0]
+ netaddr, bits = net.split('/')
+ netmask = struct.unpack('I', socket.inet_aton(netaddr))[0] & ((2 << int(bits) - 1) - 1)
+ return ipaddr & netmask == netmask
+
+
+def _is_no_proxy_host(hostname, no_proxy):
+ if not no_proxy:
+ v = os.environ.get("no_proxy", "").replace(" ", "")
+ if v:
+ no_proxy = v.split(",")
+ if not no_proxy:
+ no_proxy = DEFAULT_NO_PROXY_HOST
+
+ if hostname in no_proxy:
+ return True
+ elif _is_ip_address(hostname):
+ return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)])
+
+ return False
+
+
+def get_proxy_info(
+ hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None,
+ no_proxy=None, proxy_type='http'):
+ """
+ Try to retrieve proxy host and port from environment
+ if not provided in options.
+ Result is (proxy_host, proxy_port, proxy_auth).
+ proxy_auth is tuple of username and password
+ of proxy authentication information.
+
+ Parameters
+ ----------
+ hostname:
+ websocket server name.
+ is_secure:
+ is the connection secure? (wss) looks for "https_proxy" in env
+ before falling back to "http_proxy"
+ options:
+ - http_proxy_host:
+ http proxy host name.
+ - http_proxy_port:
+ http proxy port.
+ - http_no_proxy:
+ host names, which doesn't use proxy.
+ - http_proxy_auth:
+ http proxy auth information. tuple of username and password. default is None
+ - proxy_type:
+ if set to "socks5" PySocks wrapper will be used in place of a http proxy. default is "http"
+ """
+ if _is_no_proxy_host(hostname, no_proxy):
+ return None, 0, None
+
+ if proxy_host:
+ port = proxy_port
+ auth = proxy_auth
+ return proxy_host, port, auth
+
+ env_keys = ["http_proxy"]
+ if is_secure:
+ env_keys.insert(0, "https_proxy")
+
+ for key in env_keys:
+ value = os.environ.get(key, None)
+ if value:
+ proxy = urlparse(value)
+ auth = (proxy.username, proxy.password) if proxy.username else None
+ return proxy.hostname, proxy.port, auth
+
+ return None, 0, None
diff --git a/resources/lib/websocket/_utils.py b/resources/lib/websocket/_utils.py
new file mode 100644
index 00000000..0072bce8
--- /dev/null
+++ b/resources/lib/websocket/_utils.py
@@ -0,0 +1,110 @@
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import six
+
+__all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"]
+
+
+class NoLock(object):
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ pass
+
+
+try:
+ # If wsaccel is available we use compiled routines to validate UTF-8
+ # strings.
+ from wsaccel.utf8validator import Utf8Validator
+
+ def _validate_utf8(utfbytes):
+ return Utf8Validator().validate(utfbytes)[0]
+
+except ImportError:
+ # UTF-8 validator
+ # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+
+ _UTF8_ACCEPT = 0
+ _UTF8_REJECT = 12
+
+ _UTF8D = [
+ # The first part of the table maps bytes to character classes that
+ # to reduce the size of the transition table and create bitmasks.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+ # The second part is a transition table that maps a combination
+ # of a state of the automaton and a character class to a state.
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12, ]
+
+ def _decode(state, codep, ch):
+ tp = _UTF8D[ch]
+
+ codep = (ch & 0x3f) | (codep << 6) if (
+ state != _UTF8_ACCEPT) else (0xff >> tp) & ch
+ state = _UTF8D[256 + state + tp]
+
+ return state, codep
+
+ def _validate_utf8(utfbytes):
+ state = _UTF8_ACCEPT
+ codep = 0
+ for i in utfbytes:
+ if six.PY2:
+ i = ord(i)
+ state, codep = _decode(state, codep, i)
+ if state == _UTF8_REJECT:
+ return False
+
+ return True
+
+
+def validate_utf8(utfbytes):
+ """
+ validate utf8 byte string.
+ utfbytes: utf byte string to check.
+ return value: if valid utf8 string, return true. Otherwise, return false.
+ """
+ return _validate_utf8(utfbytes)
+
+
+def extract_err_message(exception):
+ if exception.args:
+ return exception.args[0]
+ else:
+ return None
+
+
+def extract_error_code(exception):
+ if exception.args and len(exception.args) > 1:
+ return exception.args[0] if isinstance(exception.args[0], int) else None
diff --git a/resources/lib/websocket/tests/__init__.py b/resources/lib/websocket/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/resources/lib/websocket/tests/data/header01.txt b/resources/lib/websocket/tests/data/header01.txt
new file mode 100644
index 00000000..d44d24c2
--- /dev/null
+++ b/resources/lib/websocket/tests/data/header01.txt
@@ -0,0 +1,6 @@
+HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade: WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/resources/lib/websocket/tests/data/header02.txt b/resources/lib/websocket/tests/data/header02.txt
new file mode 100644
index 00000000..f481de92
--- /dev/null
+++ b/resources/lib/websocket/tests/data/header02.txt
@@ -0,0 +1,6 @@
+HTTP/1.1 101 WebSocket Protocol Handshake
+Connection: Upgrade
+Upgrade WebSocket
+Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
+some_header: something
+
diff --git a/resources/lib/websocket/tests/test_cookiejar.py b/resources/lib/websocket/tests/test_cookiejar.py
new file mode 100644
index 00000000..190c1151
--- /dev/null
+++ b/resources/lib/websocket/tests/test_cookiejar.py
@@ -0,0 +1,122 @@
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+import unittest
+
+from websocket._cookiejar import SimpleCookieJar
+
+try:
+ import Cookie
+except:
+ import http.cookies as Cookie
+
+
+class CookieJarTest(unittest.TestCase):
+ def testAdd(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; domain=.abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; domain=abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+ self.assertTrue("abc" not in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=.abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.add("a=b; c=d; domain=abc")
+ cookie_jar.add("e=f; domain=xyz")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("xyz"), "e=f")
+ self.assertEqual(cookie_jar.get("something"), "")
+
+ def testSet(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b")
+ self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; domain=.abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; domain=abc")
+ self.assertTrue(".abc" in cookie_jar.jar)
+ self.assertTrue("abc" not in cookie_jar.jar)
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=abc")
+ self.assertEqual(cookie_jar.get("abc"), "e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=.abc")
+ self.assertEqual(cookie_jar.get("abc"), "e=f")
+
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc")
+ cookie_jar.set("e=f; domain=xyz")
+ self.assertEqual(cookie_jar.get("abc"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("xyz"), "e=f")
+ self.assertEqual(cookie_jar.get("something"), "")
+
+ def testGet(self):
+ cookie_jar = SimpleCookieJar()
+ cookie_jar.set("a=b; c=d; domain=abc.com")
+ self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("abc.com.es"), "")
+ self.assertEqual(cookie_jar.get("xabc.com"), "")
+
+ cookie_jar.set("a=b; c=d; domain=.abc.com")
+ self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d")
+ self.assertEqual(cookie_jar.get("abc.com.es"), "")
+ self.assertEqual(cookie_jar.get("xabc.com"), "")
diff --git a/resources/lib/websocket/tests/test_websocket.py b/resources/lib/websocket/tests/test_websocket.py
new file mode 100644
index 00000000..a2953dd8
--- /dev/null
+++ b/resources/lib/websocket/tests/test_websocket.py
@@ -0,0 +1,691 @@
+# -*- coding: utf-8 -*-
+#
+"""
+
+"""
+
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""
+
+import sys
+sys.path[0:0] = [""]
+
+import os
+import os.path
+import socket
+
+import six
+
+# websocket-client
+import websocket as ws
+from websocket._handshake import _create_sec_websocket_key, \
+ _validate as _validate_header
+from websocket._http import read_headers
+from websocket._url import get_proxy_info, parse_url
+from websocket._utils import validate_utf8
+
+if six.PY3:
+ from base64 import decodebytes as base64decode
+else:
+ from base64 import decodestring as base64decode
+
+if sys.version_info[0] == 2 and sys.version_info[1] < 7:
+ import unittest2 as unittest
+else:
+ import unittest
+
+try:
+ from ssl import SSLError
+except ImportError:
+ # dummy class of SSLError for ssl none-support environment.
+ class SSLError(Exception):
+ pass
+
+# Skip test to access the internet.
+TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1'
+
+# Skip Secure WebSocket test.
+TEST_SECURE_WS = True
+TRACEABLE = True
+
+
+def create_mask_key(_):
+ return "abcd"
+
+
+class SockMock(object):
+ def __init__(self):
+ self.data = []
+ self.sent = []
+
+ def add_packet(self, data):
+ self.data.append(data)
+
+ def gettimeout(self):
+ return None
+
+ def recv(self, bufsize):
+ if self.data:
+ e = self.data.pop(0)
+ if isinstance(e, Exception):
+ raise e
+ if len(e) > bufsize:
+ self.data.insert(0, e[bufsize:])
+ return e[:bufsize]
+
+ def send(self, data):
+ self.sent.append(data)
+ return len(data)
+
+ def close(self):
+ pass
+
+
+class HeaderSockMock(SockMock):
+
+ def __init__(self, fname):
+ SockMock.__init__(self)
+ path = os.path.join(os.path.dirname(__file__), fname)
+ with open(path, "rb") as f:
+ self.add_packet(f.read())
+
+
+class WebSocketTest(unittest.TestCase):
+ def setUp(self):
+ ws.enableTrace(TRACEABLE)
+
+ def tearDown(self):
+ pass
+
+ def testDefaultTimeout(self):
+ self.assertEqual(ws.getdefaulttimeout(), None)
+ ws.setdefaulttimeout(10)
+ self.assertEqual(ws.getdefaulttimeout(), 10)
+ ws.setdefaulttimeout(None)
+
+ def testParseUrl(self):
+ p = parse_url("ws://www.example.com/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com/r/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080/")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://www.example.com:8080")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("wss://www.example.com:8080/r")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+ p = parse_url("wss://www.example.com:8080/r?key=value")
+ self.assertEqual(p[0], "www.example.com")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r?key=value")
+ self.assertEqual(p[3], True)
+
+ self.assertRaises(ValueError, parse_url, "http://www.example.com/r")
+
+ if sys.version_info[0] == 2 and sys.version_info[1] < 7:
+ return
+
+ p = parse_url("ws://[2a03:4000:123:83::3]/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 80)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("ws://[2a03:4000:123:83::3]:8080/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], False)
+
+ p = parse_url("wss://[2a03:4000:123:83::3]/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 443)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+ p = parse_url("wss://[2a03:4000:123:83::3]:8080/r")
+ self.assertEqual(p[0], "2a03:4000:123:83::3")
+ self.assertEqual(p[1], 8080)
+ self.assertEqual(p[2], "/r")
+ self.assertEqual(p[3], True)
+
+ def testWSKey(self):
+ key = _create_sec_websocket_key()
+ self.assertTrue(key != 24)
+ self.assertTrue(six.u("¥n") not in key)
+
+ def testWsUtils(self):
+ key = "c6b8hTg4EeGb2gQMztV1/g=="
+ required_header = {
+ "upgrade": "websocket",
+ "connection": "upgrade",
+ "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=",
+ }
+ self.assertEqual(_validate_header(required_header, key, None), (True, None))
+
+ header = required_header.copy()
+ header["upgrade"] = "http"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["upgrade"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["connection"] = "something"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["connection"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-accept"] = "something"
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+ del header["sec-websocket-accept"]
+ self.assertEqual(_validate_header(header, key, None), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-protocol"] = "sub1"
+ self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1"))
+ self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None))
+
+ header = required_header.copy()
+ header["sec-websocket-protocol"] = "sUb1"
+ self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1"))
+
+ header = required_header.copy()
+ self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (False, None))
+
+ def testReadHeader(self):
+ status, header, status_message = read_headers(HeaderSockMock("data/header01.txt"))
+ self.assertEqual(status, 101)
+ self.assertEqual(header["connection"], "Upgrade")
+
+ HeaderSockMock("data/header02.txt")
+ self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt"))
+
+ def testSend(self):
+ # TODO: add longer frame data
+ sock = ws.WebSocket()
+ sock.set_mask_key(create_mask_key)
+ s = sock.sock = HeaderSockMock("data/header01.txt")
+ sock.send("Hello")
+ self.assertEqual(s.sent[0], six.b("\x81\x85abcd)\x07\x0f\x08\x0e"))
+
+ sock.send("こんにちは")
+ self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc"))
+
+ sock.send(u"こんにちは")
+ self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc"))
+
+ sock.send("x" * 127)
+
+ def testRecv(self):
+ # TODO: add longer frame data
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ something = six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")
+ s.add_packet(something)
+ data = sock.recv()
+ self.assertEqual(data, "こんにちは")
+
+ s.add_packet(six.b("\x81\x85abcd)\x07\x0f\x08\x0e"))
+ data = sock.recv()
+ self.assertEqual(data, "Hello")
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testIter(self):
+ count = 2
+ for _ in ws.create_connection('wss://stream.meetup.com/2/rsvps'):
+ count -= 1
+ if count == 0:
+ break
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testNext(self):
+ sock = ws.create_connection('wss://stream.meetup.com/2/rsvps')
+ self.assertEqual(str, type(next(sock)))
+
+ def testInternalRecvStrict(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ s.add_packet(six.b("foo"))
+ s.add_packet(socket.timeout())
+ s.add_packet(six.b("bar"))
+ # s.add_packet(SSLError("The read operation timed out"))
+ s.add_packet(six.b("baz"))
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.frame_buffer.recv_strict(9)
+ # if six.PY2:
+ # with self.assertRaises(ws.WebSocketTimeoutException):
+ # data = sock._recv_strict(9)
+ # else:
+ # with self.assertRaises(SSLError):
+ # data = sock._recv_strict(9)
+ data = sock.frame_buffer.recv_strict(9)
+ self.assertEqual(data, six.b("foobarbaz"))
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.frame_buffer.recv_strict(1)
+
+ def testRecvTimeout(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ s.add_packet(six.b("\x81"))
+ s.add_packet(socket.timeout())
+ s.add_packet(six.b("\x8dabcd\x29\x07\x0f\x08\x0e"))
+ s.add_packet(socket.timeout())
+ s.add_packet(six.b("\x4e\x43\x33\x0e\x10\x0f\x00\x40"))
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.recv()
+ with self.assertRaises(ws.WebSocketTimeoutException):
+ sock.recv()
+ data = sock.recv()
+ self.assertEqual(data, "Hello, World!")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithSimpleFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Brevity is "
+ s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C"))
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17"))
+ data = sock.recv()
+ self.assertEqual(data, "Brevity is the soul of wit")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithFireEventOfFragmentation(self):
+ sock = ws.WebSocket(fire_cont_frame=True)
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Brevity is "
+ s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C"))
+ # OPCODE=CONT, FIN=0, MSG="Brevity is "
+ s.add_packet(six.b("\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C"))
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17"))
+
+ _, data = sock.recv_data()
+ self.assertEqual(data, six.b("Brevity is "))
+ _, data = sock.recv_data()
+ self.assertEqual(data, six.b("Brevity is "))
+ _, data = sock.recv_data()
+ self.assertEqual(data, six.b("the soul of wit"))
+
+ # OPCODE=CONT, FIN=0, MSG="Brevity is "
+ s.add_packet(six.b("\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C"))
+
+ with self.assertRaises(ws.WebSocketException):
+ sock.recv_data()
+
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testClose(self):
+ sock = ws.WebSocket()
+ sock.sock = SockMock()
+ sock.connected = True
+ sock.close()
+ self.assertEqual(sock.connected, False)
+
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ sock.connected = True
+ s.add_packet(six.b('\x88\x80\x17\x98p\x84'))
+ sock.recv()
+ self.assertEqual(sock.connected, False)
+
+ def testRecvContFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=CONT, FIN=1, MSG="the soul of wit"
+ s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17"))
+ self.assertRaises(ws.WebSocketException, sock.recv)
+
+ def testRecvWithProlongedFragmentation(self):
+ sock = ws.WebSocket()
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, "
+ s.add_packet(six.b("\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15"
+ "\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC"))
+ # OPCODE=CONT, FIN=0, MSG="dear friends, "
+ s.add_packet(six.b("\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07"
+ "\x17MB"))
+ # OPCODE=CONT, FIN=1, MSG="once more"
+ s.add_packet(six.b("\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04"))
+ data = sock.recv()
+ self.assertEqual(
+ data,
+ "Once more unto the breach, dear friends, once more")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+
+ def testRecvWithFragmentationAndControlFrame(self):
+ sock = ws.WebSocket()
+ sock.set_mask_key(create_mask_key)
+ s = sock.sock = SockMock()
+ # OPCODE=TEXT, FIN=0, MSG="Too much "
+ s.add_packet(six.b("\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA"))
+ # OPCODE=PING, FIN=1, MSG="Please PONG this"
+ s.add_packet(six.b("\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17"))
+ # OPCODE=CONT, FIN=1, MSG="of a good thing"
+ s.add_packet(six.b("\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c"
+ "\x08\x0c\x04"))
+ data = sock.recv()
+ self.assertEqual(data, "Too much of a good thing")
+ with self.assertRaises(ws.WebSocketConnectionClosedException):
+ sock.recv()
+ self.assertEqual(
+ s.sent[0],
+ six.b("\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17"))
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testWebSocket(self):
+ s = ws.create_connection("ws://echo.websocket.org/")
+ self.assertNotEqual(s, None)
+ s.send("Hello, World")
+ result = s.recv()
+ self.assertEqual(result, "Hello, World")
+
+ s.send(u"こにゃにゃちは、世界")
+ result = s.recv()
+ self.assertEqual(result, "こにゃにゃちは、世界")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testPingPong(self):
+ s = ws.create_connection("ws://echo.websocket.org/")
+ self.assertNotEqual(s, None)
+ s.ping("Hello")
+ s.pong("Hi")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ @unittest.skipUnless(TEST_SECURE_WS, "wss://echo.websocket.org doesn't work well.")
+ def testSecureWebSocket(self):
+ if 1:
+ import ssl
+ s = ws.create_connection("wss://echo.websocket.org/")
+ self.assertNotEqual(s, None)
+ self.assertTrue(isinstance(s.sock, ssl.SSLSocket))
+ s.send("Hello, World")
+ result = s.recv()
+ self.assertEqual(result, "Hello, World")
+ s.send(u"こにゃにゃちは、世界")
+ result = s.recv()
+ self.assertEqual(result, "こにゃにゃちは、世界")
+ s.close()
+ #except:
+ # pass
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testWebSocketWithCustomHeader(self):
+ s = ws.create_connection("ws://echo.websocket.org/",
+ headers={"User-Agent": "PythonWebsocketClient"})
+ self.assertNotEqual(s, None)
+ s.send("Hello, World")
+ result = s.recv()
+ self.assertEqual(result, "Hello, World")
+ s.close()
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testAfterClose(self):
+ s = ws.create_connection("ws://echo.websocket.org/")
+ self.assertNotEqual(s, None)
+ s.close()
+ self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello")
+ self.assertRaises(ws.WebSocketConnectionClosedException, s.recv)
+
+ def testNonce(self):
+ """ WebSocket key should be a random 16-byte nonce.
+ """
+ key = _create_sec_websocket_key()
+ nonce = base64decode(key.encode("utf-8"))
+ self.assertEqual(16, len(nonce))
+
+
+class WebSocketAppTest(unittest.TestCase):
+
+ class NotSetYet(object):
+ """ A marker class for signalling that a value hasn't been set yet.
+ """
+
+ def setUp(self):
+ ws.enableTrace(TRACEABLE)
+
+ WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
+ WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
+ WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
+
+ def tearDown(self):
+ WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet()
+ WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet()
+ WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet()
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testKeepRunning(self):
+ """ A WebSocketApp should keep running as long as its self.keep_running
+ is not False (in the boolean context).
+ """
+
+ def on_open(self, *args, **kwargs):
+ """ Set the keep_running flag for later inspection and immediately
+ close the connection.
+ """
+ WebSocketAppTest.keep_running_open = self.keep_running
+
+ self.close()
+
+ def on_close(self, *args, **kwargs):
+ """ Set the keep_running flag for the test to use.
+ """
+ WebSocketAppTest.keep_running_close = self.keep_running
+
+ app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close)
+ app.run_forever()
+
+ # if numpy is installed, this assertion fail
+ # self.assertFalse(isinstance(WebSocketAppTest.keep_running_open,
+ # WebSocketAppTest.NotSetYet))
+
+ # self.assertFalse(isinstance(WebSocketAppTest.keep_running_close,
+ # WebSocketAppTest.NotSetYet))
+
+ # self.assertEqual(True, WebSocketAppTest.keep_running_open)
+ # self.assertEqual(False, WebSocketAppTest.keep_running_close)
+
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testSockMaskKey(self):
+ """ A WebSocketApp should forward the received mask_key function down
+ to the actual socket.
+ """
+
+ def my_mask_key_func():
+ pass
+
+ def on_open(self, *args, **kwargs):
+ """ Set the value so the test can use it later on and immediately
+ close the connection.
+ """
+ WebSocketAppTest.get_mask_key_id = id(self.get_mask_key)
+ self.close()
+
+ app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func)
+ app.run_forever()
+
+ # if numpu is installed, this assertion fail
+ # Note: We can't use 'is' for comparing the functions directly, need to use 'id'.
+ # self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func))
+
+
+class SockOptTest(unittest.TestCase):
+ @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled")
+ def testSockOpt(self):
+ sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),)
+ s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt)
+ self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0)
+ s.close()
+
+
+class UtilsTest(unittest.TestCase):
+ def testUtf8Validator(self):
+ state = validate_utf8(six.b('\xf0\x90\x80\x80'))
+ self.assertEqual(state, True)
+ state = validate_utf8(six.b('\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited'))
+ self.assertEqual(state, False)
+ state = validate_utf8(six.b(''))
+ self.assertEqual(state, True)
+
+
+class ProxyInfoTest(unittest.TestCase):
+ def setUp(self):
+ self.http_proxy = os.environ.get("http_proxy", None)
+ self.https_proxy = os.environ.get("https_proxy", None)
+ if "http_proxy" in os.environ:
+ del os.environ["http_proxy"]
+ if "https_proxy" in os.environ:
+ del os.environ["https_proxy"]
+
+ def tearDown(self):
+ if self.http_proxy:
+ os.environ["http_proxy"] = self.http_proxy
+ elif "http_proxy" in os.environ:
+ del os.environ["http_proxy"]
+
+ if self.https_proxy:
+ os.environ["https_proxy"] = self.https_proxy
+ elif "https_proxy" in os.environ:
+ del os.environ["https_proxy"]
+
+ def testProxyFromArgs(self):
+ self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None))
+ self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None))
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None))
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None))
+
+ self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")),
+ ("localhost", 0, ("a", "b")))
+ self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
+ ("localhost", 3128, ("a", "b")))
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")),
+ ("localhost", 0, ("a", "b")))
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")),
+ ("localhost", 3128, ("a", "b")))
+
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["example.com"], proxy_auth=("a", "b")),
+ ("localhost", 3128, ("a", "b")))
+ self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")),
+ (None, 0, None))
+
+ def testProxyFromEnv(self):
+ os.environ["http_proxy"] = "http://localhost/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
+ os.environ["http_proxy"] = "http://localhost:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
+
+ os.environ["http_proxy"] = "http://localhost/"
+ os.environ["https_proxy"] = "http://localhost2/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None))
+ os.environ["http_proxy"] = "http://localhost:3128/"
+ os.environ["https_proxy"] = "http://localhost2:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None))
+
+ os.environ["http_proxy"] = "http://localhost/"
+ os.environ["https_proxy"] = "http://localhost2/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None))
+ os.environ["http_proxy"] = "http://localhost:3128/"
+ os.environ["https_proxy"] = "http://localhost2:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None))
+
+
+ os.environ["http_proxy"] = "http://a:b@localhost/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
+ os.environ["http_proxy"] = "http://a:b@localhost:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
+
+ os.environ["http_proxy"] = "http://a:b@localhost/"
+ os.environ["https_proxy"] = "http://a:b@localhost2/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b")))
+ os.environ["http_proxy"] = "http://a:b@localhost:3128/"
+ os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b")))
+
+ os.environ["http_proxy"] = "http://a:b@localhost/"
+ os.environ["https_proxy"] = "http://a:b@localhost2/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b")))
+ os.environ["http_proxy"] = "http://a:b@localhost:3128/"
+ os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
+ self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b")))
+
+ os.environ["http_proxy"] = "http://a:b@localhost/"
+ os.environ["https_proxy"] = "http://a:b@localhost2/"
+ os.environ["no_proxy"] = "example1.com,example2.com"
+ self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b")))
+ os.environ["http_proxy"] = "http://a:b@localhost:3128/"
+ os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
+ os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org"
+ self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None))
+
+ os.environ["http_proxy"] = "http://a:b@localhost:3128/"
+ os.environ["https_proxy"] = "http://a:b@localhost2:3128/"
+ os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16"
+ self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None))
+ self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py
index 694c264c..6184cb22 100644
--- a/resources/lib/websocket_client.py
+++ b/resources/lib/websocket_client.py
@@ -1,58 +1,160 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
-from json import loads
-from ssl import CERT_NONE
+import json
-from . import backgroundthread, websocket, utils, companion, app, variables as v
+from . import websocket
+from . import backgroundthread, app, variables as v, utils, companion
-###############################################################################
+log = getLogger('PLEX.websocket')
-LOG = getLogger('PLEX.websocket_client')
+PMS_PATH = '/:/websockets/notifications'
-###############################################################################
+PMS_INTERESTING_MESSAGE_TYPES = ('playing', 'timeline', 'activity')
+SETTINGS_STRING = '_status'
-class WebSocket(backgroundthread.KillableThread):
- opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
- status_setting = None
+def get_pms_uri():
+ uri = app.CONN.server
+ if not uri:
+ return
+ # Get the appropriate prefix for the websocket
+ if uri.startswith('https'):
+ uri = "wss%s" % uri[5:]
+ else:
+ uri = "ws%s" % uri[4:]
+ uri += PMS_PATH
+ log.debug('uri to connect pms websocket: %s', uri)
+ if app.ACCOUNT.pms_token:
+ uri += '?X-Plex-Token=' + app.ACCOUNT.pms_token
+ return uri
- def __init__(self):
- self.ws = None
- self.redirect_uri = None
- self.sleeptime = 0.0
- super(WebSocket, self).__init__()
- def close_websocket(self):
- if self.ws is not None:
- self.ws.close()
- self.ws = None
+def get_alexa_uri():
+ if not app.ACCOUNT.plex_token:
+ return
+ return ('wss://pubsub.plex.tv/sub/websockets/%s/%s?X-Plex-Token=%s'
+ % (app.ACCOUNT.plex_user_id,
+ v.PKC_MACHINE_IDENTIFIER,
+ app.ACCOUNT.plex_token))
- def process(self, opcode, message):
- raise NotImplementedError
- def receive(self, ws):
- # Not connected yet
- if ws is None:
- raise websocket.WebSocketConnectionClosedException
+def pms_on_message(ws, message):
+ """
+ Called when we receive a message from the PMS, e.g. when a new library
+ item has been added.
+ """
+ try:
+ message = json.loads(message)
+ except ValueError as err:
+ log.error('Error decoding PMS websocket message: %s', err)
+ log.error('message: %s', message)
+ return
+ try:
+ message = message['NotificationContainer']
+ typus = message['type']
+ except KeyError:
+ log.error('Could not parse PMS message: %s', message)
+ return
+ # Triage
+ if typus not in PMS_INTERESTING_MESSAGE_TYPES:
+ # Drop everything we're not interested in
+ return
+ else:
+ # Put PMS message on queue and let libsync take care of it
+ app.APP.websocket_queue.put(message)
- frame = ws.recv_frame()
- if not frame:
- raise websocket.WebSocketException("Not a valid frame %s" % frame)
- elif frame.opcode in self.opcode_data:
- return frame.opcode, frame.data
- elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
- ws.send_close()
- return frame.opcode, None
- elif frame.opcode == websocket.ABNF.OPCODE_PING:
- ws.pong("Hi!")
- return None, None
+def alexa_on_message(ws, message):
+ """
+ Called when we receive a message from Alexa
+ """
+ log.debug('alexa message received: %s', message)
+ try:
+ message = utils.etree.fromstring(message)
+ except Exception as err:
+ log.error('Error decoding message from Alexa: %s %s', type(err), err)
+ log.error('message from Alexa: %s', message)
+ return
+ try:
+ if message.attrib['command'] == 'processRemoteControlCommand':
+ message = message[0]
+ else:
+ log.error('Unknown Alexa message received: %s', message)
+ return
+ companion.process_command(message.attrib['path'][1:], message.attrib)
+ except Exception as err:
+ log.exception('Could not parse Alexa message, error: %s %s',
+ type(err), err)
+ log.error('message: %s', message)
- def getUri(self):
- raise NotImplementedError
- def _sleep_cycle(self):
+def on_error(ws, error):
+ status = ws.name + SETTINGS_STRING
+ if isinstance(error, IOError):
+ # We are probably offline
+ log.debug('%s: IOError connecting', ws.name)
+ # Status = IOError - not connected
+ utils.settings(status, value=utils.lang(39092))
+ ws.sleep_cycle()
+ elif isinstance(error, websocket.WebSocketTimeoutException):
+ log.debug('%s: WebSocketTimeoutException', ws.name)
+ # Status = 'Timeout - not connected'
+ utils.settings(status, value=utils.lang(39091))
+ ws.sleep_cycle()
+ elif isinstance(error, websocket.WebSocketConnectionClosedException):
+ log.debug('%s: WebSocketConnectionClosedException', ws.name)
+ # Status = Not connected
+ utils.settings(ws.name + SETTINGS_STRING, value=utils.lang(15208))
+ elif isinstance(error, websocket.WebSocketBadStatusException):
+ # Most likely Alexa not connecting, throwing a 403
+ log.debug('%s: got a bad HTTP status: %s', ws.name, error)
+ # Status =
+ utils.settings(status, value=str(error))
+ ws.sleep_cycle()
+ elif isinstance(error, websocket.WebSocketException):
+ log.error('%s: got another websocket exception %s: %s',
+ ws.name, type(error), error)
+ # Status = Error
+ utils.settings(status, value=utils.lang(257))
+ ws.sleep_cycle()
+ elif isinstance(error, SystemExit):
+ log.debug('%s: SystemExit detected', ws.name)
+ # Status = Not connected
+ utils.settings(ws.name + SETTINGS_STRING, value=utils.lang(15208))
+ else:
+ log.exception('%s: got an unexpected exception of type %s: %s',
+ ws.name, type(error), error)
+ # Status = Error
+ utils.settings(status, value=utils.lang(257))
+ raise RuntimeError
+
+
+def on_close(ws):
+ """
+ This does not seem to get called by our websocket client :-(
+ """
+ log.debug('%s: connection closed', ws.name)
+ # Status = Not connected
+ utils.settings(ws.name + SETTINGS_STRING, value=utils.lang(15208))
+
+
+def on_open(ws):
+ log.debug('%s: connected', ws.name)
+ # Status = Connected
+ utils.settings(ws.name + SETTINGS_STRING, value=utils.lang(13296))
+ ws.sleeptime = 0
+
+
+class PlexWebSocketApp(websocket.WebSocketApp,
+ backgroundthread.KillableThread):
+ def __init__(self, **kwargs):
+ self.sleeptime = 0
+ backgroundthread.KillableThread.__init__(self)
+ websocket.WebSocketApp.__init__(self, self.get_uri(), **kwargs)
+
+ def sleep_cycle(self):
"""
Sleeps for 2^self.sleeptime where sleeping period will be doubled with
each unsuccessful connection attempt.
@@ -60,226 +162,133 @@ class WebSocket(backgroundthread.KillableThread):
"""
self.sleep(2 ** self.sleeptime)
if self.sleeptime < 6:
- self.sleeptime += 1.0
+ self.sleeptime += 1
+
+ def suspend(self, block=False, timeout=None):
+ """
+ Call this method from another thread to suspend this websocket thread
+ """
+ self.close()
+ backgroundthread.KillableThread.suspend(self, block, timeout)
+
+ def cancel(self):
+ """
+ Call this method from another thread to cancel this websocket thread
+ """
+ self.close()
+ backgroundthread.KillableThread.cancel(self)
def run(self):
- LOG.info("----===## Starting %s ##===----", self.__class__.__name__)
+ """
+ Ensure that sockets will be closed no matter what
+ """
+ log.info("----===## Starting %s ##===----", self.name)
app.APP.register_thread(self)
try:
self._run()
+ except RuntimeError:
+ pass
+ except Exception as err:
+ log.exception('Exception of type %s occured: %s', type(err), err)
finally:
- self.close_websocket()
+ self.close()
+ # Status = Not connected
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(15208))
app.APP.deregister_thread(self)
- LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
+ log.info("----===## %s stopped ##===----", self.name)
def _run(self):
while not self.should_cancel():
# In the event the server goes offline
- if self.should_suspend():
- # Set in service.py
- self.close_websocket()
- # Status = 'Suspended - not connected'
- utils.settings(self.status_setting, value=utils.lang(39090))
+ while self.should_suspend():
+ # We will be caught in this loop if either another thread
+ # called the suspend() method, thus setting _suspended = True
+ # OR if there any other conditions to not open a websocket
+ # connection - see methods should_suspend() below
+ # Status = Suspended - not connected
+ self.set_suspension_settings_status()
if self.wait_while_suspended():
# Abort was requested while waiting. We should exit
return
- try:
- self.process(*self.receive(self.ws))
- except websocket.WebSocketTimeoutException:
- # No worries if read timed out
- pass
- except websocket.WebSocketConnectionClosedException:
- LOG.debug("%s: connection closed, (re)connecting",
- self.__class__.__name__)
- uri, sslopt = self.getUri()
- try:
- # Low timeout - let's us shut this thread down!
- self.ws = websocket.create_connection(
- uri,
- timeout=1,
- sslopt=sslopt,
- enable_multithread=True)
- except IOError:
- # Server is probably offline
- LOG.debug("%s: IOError connecting", self.__class__.__name__)
- self.ws = None
- # Status = IOError - not connected
- utils.settings(self.status_setting,
- value=utils.lang(39092))
- self._sleep_cycle()
- except websocket.WebSocketTimeoutException:
- LOG.debug("%s: WebSocketTimeoutException", self.__class__.__name__)
- self.ws = None
- # Status = 'Timeout - not connected'
- utils.settings(self.status_setting,
- value=utils.lang(39091))
- self._sleep_cycle()
- except websocket.WebsocketRedirect as e:
- LOG.debug('301 redirect detected: %s', e)
- self.redirect_uri = e.headers.get('location',
- e.headers.get('Location'))
- if self.redirect_uri:
- self.redirect_uri = self.redirect_uri.decode('utf-8')
- self.ws = None
- self._sleep_cycle()
- except websocket.WebSocketException as e:
- LOG.debug('%s: WebSocketException: %s', self.__class__.__name__, e)
- self.ws = None
- # Status = Error
- utils.settings(self.status_setting,
- value=utils.lang(257))
- self._sleep_cycle()
- except Exception as e:
- LOG.error('%s: Unknown exception encountered when '
- 'connecting: %s', self.__class__.__name__, e)
- import traceback
- LOG.error("%s: Traceback:\n%s",
- self.__class__.__name__, traceback.format_exc())
- self.ws = None
- # Status = Error
- utils.settings(self.status_setting,
- value=utils.lang(257))
- self._sleep_cycle()
- else:
- self.sleeptime = 0.0
- # Status = Connected
- utils.settings(self.status_setting,
- value=utils.lang(13296))
- except Exception as e:
- LOG.error("%s: Unknown exception encountered: %s",
- self.__class__.__name__, e)
- import traceback
- LOG.error("%s: Traceback:\n%s",
- self.__class__.__name__, traceback.format_exc())
- self.close_websocket()
- # Status = Error
- utils.settings(self.status_setting, value=utils.lang(257))
+ if not self._suspended:
+ # because wait_while_suspended will return instantly if
+ # this thread did not get suspended from another thread
+ self.sleep_cycle()
+ self.url = self.get_uri()
+ if not self.url:
+ self.sleep_cycle()
+ continue
+ self.run_forever()
-class PMS_Websocket(WebSocket):
- """
- Websocket connection with the PMS for Plex Companion
- """
- status_setting = 'pms_websocket_status'
+class PMSWebsocketApp(PlexWebSocketApp):
+ name = 'pms_websocket'
+
+ def get_uri(self):
+ return get_pms_uri()
def should_suspend(self):
"""
- Returns True if the thread is suspended.
+ Returns True if the thread needs to suspend.
"""
- suspend = self._suspended or app.SYNC.background_sync_disabled
- if suspend:
- # This thread needs to clear the Event() _is_not_suspended itself!
- self.suspend()
- return suspend
+ return (self._suspended or
+ utils.settings('enableBackgroundSync') != 'true')
- def getUri(self):
- if self.redirect_uri:
- uri = self.redirect_uri
- self.redirect_uri = None
+ def set_suspension_settings_status(self):
+ if utils.settings('enableBackgroundSync') != 'true':
+ # Status = Disabled
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(24023))
else:
- server = app.CONN.server
- # Get the appropriate prefix for the websocket
- if server.startswith('https'):
- server = "wss%s" % server[5:]
- else:
- server = "ws%s" % server[4:]
- uri = "%s/:/websockets/notifications" % server
- if app.ACCOUNT.pms_token:
- uri += '?X-Plex-Token=%s' % app.ACCOUNT.pms_token
- sslopt = {}
- if v.KODIVERSION == 17 and utils.settings('sslverify') == "false":
- sslopt["cert_reqs"] = CERT_NONE
- LOG.debug("%s: Uri: %s, sslopt: %s",
- self.__class__.__name__, uri, sslopt)
- return uri, sslopt
-
- def process(self, opcode, message):
- if opcode not in self.opcode_data:
- return
-
- try:
- message = loads(message)
- except ValueError as err:
- LOG.error('%s: Error decoding message from websocket: %s',
- self.__class__.__name__, err)
- LOG.error(message)
- return
- try:
- message = message['NotificationContainer']
- except KeyError:
- LOG.error('%s: Could not parse PMS message: %s',
- self.__class__.__name__, message)
- return
- # Triage
- typus = message.get('type')
- if typus is None:
- LOG.error('%s: No message type, dropping message: %s',
- self.__class__.__name__, message)
- return
- LOG.debug('%s: Received message from PMS server: %s',
- self.__class__.__name__, message)
- # Drop everything we're not interested in
- if typus not in ('playing', 'timeline', 'activity'):
- return
- else:
- # Put PMS message on queue and let libsync take care of it
- app.APP.websocket_queue.put(message)
+ # Status = 'Suspended - not connected'
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(39093))
-class Alexa_Websocket(WebSocket):
- """
- Websocket connection to talk to Amazon Alexa.
- """
- status_setting = 'alexa_websocket_status'
+class AlexaWebsocketApp(PlexWebSocketApp):
+ name = 'alexa_websocket'
+
+ def get_uri(self):
+ return get_alexa_uri()
def should_suspend(self):
"""
- Overwrite method since we need to check for plex token
+ Returns True if the thread needs to suspend.
"""
- suspend = self._suspended or \
- not app.SYNC.enable_alexa or \
+ return self._suspended or \
+ utils.settings('enable_alexa') != 'true' or \
app.ACCOUNT.restricted_user or \
not app.ACCOUNT.plex_token
- if suspend:
- # This thread needs to clear the Event() _is_not_suspended itself!
- self.suspend()
- return suspend
- def getUri(self):
- if self.redirect_uri:
- uri = self.redirect_uri
- self.redirect_uri = None
+ def set_suspension_settings_status(self):
+ if utils.settings('enable_alexa') != 'true':
+ # Status = Disabled
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(24023))
+ elif app.ACCOUNT.restricted_user:
+ # Status = Managed Plex User - not connected
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(39094))
+ elif not app.ACCOUNT.plex_token:
+ # Status = Not logged in to plex.tv
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(39226))
else:
- uri = ('wss://pubsub.plex.tv/sub/websockets/%s/%s?X-Plex-Token=%s'
- % (app.ACCOUNT.plex_user_id,
- v.PKC_MACHINE_IDENTIFIER,
- app.ACCOUNT.plex_token))
- sslopt = {}
- LOG.debug("%s: Uri: %s, sslopt: %s",
- self.__class__.__name__, uri, sslopt)
- return uri, sslopt
+ # Status = 'Suspended - not connected'
+ utils.settings(self.name + SETTINGS_STRING,
+ value=utils.lang(39093))
- def process(self, opcode, message):
- if opcode not in self.opcode_data:
- return
- LOG.debug('%s: Received the following message from Alexa:',
- self.__class__.__name__)
- LOG.debug('%s: %s', self.__class__.__name__, message)
- try:
- message = utils.defused_etree.fromstring(message)
- except Exception as ex:
- LOG.error('%s: Error decoding message from Alexa: %s',
- self.__class__.__name__, ex)
- return
- try:
- if message.attrib['command'] == 'processRemoteControlCommand':
- message = message[0]
- else:
- LOG.error('%s: Unknown Alexa message received',
- self.__class__.__name__)
- return
- except Exception:
- LOG.error('%s: Could not parse Alexa message',
- self.__class__.__name__)
- return
- companion.process_command(message.attrib['path'][1:], message.attrib)
+
+def get_pms_websocketapp():
+ return PMSWebsocketApp(on_open=on_open,
+ on_message=pms_on_message,
+ on_error=on_error,
+ on_close=on_close)
+
+
+def get_alexa_websocketapp():
+ return AlexaWebsocketApp(on_open=on_open,
+ on_message=alexa_on_message,
+ on_error=on_error,
+ on_close=on_close)