aboutsummaryrefslogtreecommitdiff
path: root/jellyfin_apiclient_python/ws_client.py
blob: d36310bf9731ac13f8614c7e6ce15c6bdd4498c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals

#################################################################################################

import json
import logging
import threading
import ssl
import certifi

import websocket

from .keepalive import KeepAlive

##################################################################################################

LOG = logging.getLogger('JELLYFIN.' + __name__)

##################################################################################################


class WSClient(threading.Thread):
    multi_client = False
    global_wsc = None
    global_stop = False

    def __init__(self, client, allow_multiple_clients=False):

        LOG.debug("WSClient initializing...")

        self.client = client
        self.keepalive = None
        self.wsc = None
        self.stop = False
        self.message_ids = set()

        if self.multi_client or allow_multiple_clients:
            self.multi_client = True

        threading.Thread.__init__(self)

    def send(self, message, data=""):
        if self.wsc is None:
            raise ValueError("The websocket client is not started.")

        self.wsc.send(json.dumps({'MessageType': message, "Data": data}))

    def run(self):

        token = self.client.config.data['auth.token']
        device_id = self.client.config.data['app.device_id']
        server = self.client.config.data['auth.server']
        server = server.replace('https', "wss") if server.startswith('https') else server.replace('http', "ws")
        wsc_url = "%s/socket?api_key=%s&device_id=%s" % (server, token, device_id)
        verify = self.client.config.data.get('auth.ssl', False)

        LOG.info("Websocket url: %s", wsc_url)

        self.wsc = websocket.WebSocketApp(wsc_url,
                                          on_message=lambda ws, message: self.on_message(ws, message),
                                          on_error=lambda ws, error: self.on_error(ws, error))
        self.wsc.on_open = lambda ws: self.on_open(ws)
        
        if not self.multi_client:
            if self.global_wsc is not None:
                self.global_wsc.close()
            self.global_wsc = self.wsc

        while not self.stop and not self.global_stop:
            if not verify:
                # https://stackoverflow.com/questions/48740053/
                self.wsc.run_forever(
                    ping_interval=10, sslopt={"cert_reqs": ssl.CERT_NONE}
                )
            else:
                self.wsc.run_forever(ping_interval=10, sslopt={"ca_certs": certifi.where()})

            if not self.stop:
                break

        LOG.info("---<[ websocket ]")
        self.client.callback('WebSocketDisconnect', None)

    def on_error(self, ws, error):
        LOG.error(error)
        self.client.callback('WebSocketError', error)

    def on_open(self, ws):
        LOG.info("--->[ websocket ]")
        self.client.callback('WebSocketConnect', None)

    def on_message(self, ws, message):

        message = json.loads(message)

        # If a message is received multiple times, ignore repeats.
        message_id = message.get("MessageId")
        if message_id is not None:
            if message_id in self.message_ids:
                return
            self.message_ids.add(message_id)

        data = message.get('Data', {})

        if message['MessageType'] == "ForceKeepAlive":
            self.send("KeepAlive")
            if self.keepalive is not None:
                self.keepalive.stop()
            self.keepalive = KeepAlive(data, self)
            self.keepalive.start()
            LOG.debug("ForceKeepAlive received from server.")
            return
        elif message['MessageType'] == "KeepAlive":
            LOG.debug("KeepAlive received from server.")
            return

        if data is None:
            data = {}
        elif type(data) is not dict:
            data = {"value": data}

        if not self.client.config.data['app.default']:
            data['ServerId'] = self.client.auth.server_id

        self.client.callback(message['MessageType'], data)

    def stop_client(self):

        self.stop = True

        if self.keepalive is not None:
            self.keepalive.stop()

        if self.wsc is not None:
            self.wsc.close()

        if not self.multi_client:
            self.global_stop = True
            self.global_wsc = None