forked from platypush/platypush
Major LINT fixes
This commit is contained in:
parent
86761e7088
commit
2a78f81a7b
101 changed files with 527 additions and 669 deletions
|
@ -3,6 +3,14 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
Given the high speed of development in the first phase, changes are being reported only starting from v0.20.2.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Major LINT fixes.
|
||||
|
||||
- Removed unmaintained integrations: TorrentCast and Booking.com
|
||||
|
||||
## [0.20.8] - 2021-04-04
|
||||
|
||||
### Added
|
||||
|
|
|
@ -28,7 +28,6 @@ Events
|
|||
platypush/events/gps.rst
|
||||
platypush/events/http.rst
|
||||
platypush/events/http.hook.rst
|
||||
platypush/events/http.ota.booking.rst
|
||||
platypush/events/http.rss.rst
|
||||
platypush/events/inotify.rst
|
||||
platypush/events/joystick.rst
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.message.event.http.ota.booking``
|
||||
============================================
|
||||
|
||||
.. automodule:: platypush.message.event.http.ota.booking
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.http.request.ota.booking``
|
||||
==============================================
|
||||
|
||||
.. automodule:: platypush.plugins.http.request.ota.booking
|
||||
:members:
|
|
@ -1,5 +0,0 @@
|
|||
``platypush.plugins.video.torrentcast``
|
||||
=======================================
|
||||
|
||||
.. automodule:: platypush.plugins.video.torrentcast
|
||||
:members:
|
|
@ -61,7 +61,6 @@ Plugins
|
|||
platypush/plugins/graphite.rst
|
||||
platypush/plugins/homeseer.rst
|
||||
platypush/plugins/http.request.rst
|
||||
platypush/plugins/http.request.ota.booking.rst
|
||||
platypush/plugins/http.request.rss.rst
|
||||
platypush/plugins/http.webpage.rst
|
||||
platypush/plugins/ifttt.rst
|
||||
|
@ -137,7 +136,6 @@ Plugins
|
|||
platypush/plugins/user.rst
|
||||
platypush/plugins/utils.rst
|
||||
platypush/plugins/variable.rst
|
||||
platypush/plugins/video.torrentcast.rst
|
||||
platypush/plugins/weather.rst
|
||||
platypush/plugins/weather.buienradar.rst
|
||||
platypush/plugins/weather.darksky.rst
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
import logging
|
||||
import re
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from threading import Thread
|
||||
from threading import Thread, Event as ThreadEvent, get_ident
|
||||
from typing import Optional, Dict
|
||||
|
||||
from platypush.bus import Bus
|
||||
|
@ -62,7 +61,7 @@ class Backend(Thread, EventGenerator):
|
|||
self.poll_seconds = float(poll_seconds) if poll_seconds else None
|
||||
self.device_id = Config.get('device_id')
|
||||
self.thread_id = None
|
||||
self._stop_event = threading.Event()
|
||||
self._stop_event = ThreadEvent()
|
||||
self._kwargs = kwargs
|
||||
self.logger = logging.getLogger('platypush:backend:' + get_backend_name_by_class(self.__class__))
|
||||
self.zeroconf = None
|
||||
|
@ -220,7 +219,7 @@ class Backend(Thread, EventGenerator):
|
|||
|
||||
def run(self):
|
||||
""" Starts the backend thread. To be implemented in the derived classes if the loop method isn't defined. """
|
||||
self.thread_id = threading.get_ident()
|
||||
self.thread_id = get_ident()
|
||||
set_thread_name(self._thread_name)
|
||||
if not callable(self.loop):
|
||||
return
|
||||
|
|
|
@ -65,11 +65,11 @@ class AdafruitIoBackend(Backend):
|
|||
def on_message(self, msg):
|
||||
# noinspection PyUnusedLocal
|
||||
def _handler(client, feed, data):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
data = float(data)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug('Not a number: {}: {}'.format(data, e))
|
||||
|
||||
self.bus.post(FeedUpdateEvent(feed=feed, data=data))
|
||||
|
||||
return _handler
|
||||
|
|
|
@ -55,15 +55,17 @@ class Alarm:
|
|||
self._runtime_snooze_interval = snooze_interval
|
||||
|
||||
def get_next(self) -> float:
|
||||
now = datetime.datetime.now().replace(tzinfo=gettz())
|
||||
now = datetime.datetime.now().replace(tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||
|
||||
try:
|
||||
cron = croniter.croniter(self.when, now)
|
||||
return cron.get_next()
|
||||
except (AttributeError, croniter.CroniterBadCronError):
|
||||
try:
|
||||
# lgtm [py/call-to-non-callable]
|
||||
timestamp = datetime.datetime.fromisoformat(self.when).replace(tzinfo=gettz())
|
||||
except (TypeError, ValueError):
|
||||
# lgtm [py/call-to-non-callable]
|
||||
timestamp = (datetime.datetime.now().replace(tzinfo=gettz()) +
|
||||
datetime.timedelta(seconds=int(self.when)))
|
||||
|
||||
|
|
|
@ -13,25 +13,28 @@ Bd addr are represented as standard python strings, e.g. "aa:bb:cc:dd:ee:ff".
|
|||
import asyncio
|
||||
from enum import Enum
|
||||
from collections import namedtuple
|
||||
import time
|
||||
import struct
|
||||
import itertools
|
||||
|
||||
|
||||
class CreateConnectionChannelError(Enum):
|
||||
NoError = 0
|
||||
MaxPendingConnectionsReached = 1
|
||||
|
||||
|
||||
class ConnectionStatus(Enum):
|
||||
Disconnected = 0
|
||||
Connected = 1
|
||||
Ready = 2
|
||||
|
||||
|
||||
class DisconnectReason(Enum):
|
||||
Unspecified = 0
|
||||
ConnectionEstablishmentFailed = 1
|
||||
TimedOut = 2
|
||||
BondingKeysMismatch = 3
|
||||
|
||||
|
||||
class RemovedReason(Enum):
|
||||
RemovedByThisClient = 0
|
||||
ForceDisconnectedByThisClient = 1
|
||||
|
@ -44,6 +47,7 @@ class RemovedReason(Enum):
|
|||
|
||||
CouldntLoadDevice = 7
|
||||
|
||||
|
||||
class ClickType(Enum):
|
||||
ButtonDown = 0
|
||||
ButtonUp = 1
|
||||
|
@ -52,20 +56,24 @@ class ClickType(Enum):
|
|||
ButtonDoubleClick = 4
|
||||
ButtonHold = 5
|
||||
|
||||
|
||||
class BdAddrType(Enum):
|
||||
PublicBdAddrType = 0
|
||||
RandomBdAddrType = 1
|
||||
|
||||
|
||||
class LatencyMode(Enum):
|
||||
NormalLatency = 0
|
||||
LowLatency = 1
|
||||
HighLatency = 2
|
||||
|
||||
|
||||
class BluetoothControllerState(Enum):
|
||||
Detached = 0
|
||||
Resetting = 1
|
||||
Attached = 2
|
||||
|
||||
|
||||
class ScanWizardResult(Enum):
|
||||
WizardSuccess = 0
|
||||
WizardCancelledByUser = 1
|
||||
|
@ -75,6 +83,7 @@ class ScanWizardResult(Enum):
|
|||
WizardInternetBackendError = 5
|
||||
WizardInvalidData = 6
|
||||
|
||||
|
||||
class ButtonScanner:
|
||||
"""ButtonScanner class.
|
||||
|
||||
|
@ -90,6 +99,7 @@ class ButtonScanner:
|
|||
self._scan_id = next(ButtonScanner._cnt)
|
||||
self.on_advertisement_packet = lambda scanner, bd_addr, name, rssi, is_private, already_verified: None
|
||||
|
||||
|
||||
class ScanWizard:
|
||||
"""ScanWizard class
|
||||
|
||||
|
@ -113,6 +123,7 @@ class ScanWizard:
|
|||
self.on_button_connected = lambda scan_wizard, bd_addr, name: None
|
||||
self.on_completed = lambda scan_wizard, result, bd_addr, name: None
|
||||
|
||||
|
||||
class ButtonConnectionChannel:
|
||||
"""ButtonConnectionChannel class.
|
||||
|
||||
|
@ -133,7 +144,7 @@ class ButtonConnectionChannel:
|
|||
|
||||
_cnt = itertools.count()
|
||||
|
||||
def __init__(self, bd_addr, latency_mode = LatencyMode.NormalLatency, auto_disconnect_time = 511):
|
||||
def __init__(self, bd_addr, latency_mode=LatencyMode.NormalLatency, auto_disconnect_time=511):
|
||||
self._conn_id = next(ButtonConnectionChannel._cnt)
|
||||
self._bd_addr = bd_addr
|
||||
self._latency_mode = latency_mode
|
||||
|
@ -164,7 +175,9 @@ class ButtonConnectionChannel:
|
|||
|
||||
self._latency_mode = latency_mode
|
||||
if not self._client._closed:
|
||||
self._client._send_command("CmdChangeModeParameters", {"conn_id": self._conn_id, "latency_mode": self._latency_mode, "auto_disconnect_time": self._auto_disconnect_time})
|
||||
self._client._send_command("CmdChangeModeParameters",
|
||||
{"conn_id": self._conn_id, "latency_mode": self._latency_mode,
|
||||
"auto_disconnect_time": self._auto_disconnect_time})
|
||||
|
||||
@property
|
||||
def auto_disconnect_time(self):
|
||||
|
@ -178,7 +191,10 @@ class ButtonConnectionChannel:
|
|||
|
||||
self._auto_disconnect_time = auto_disconnect_time
|
||||
if not self._client._closed:
|
||||
self._client._send_command("CmdChangeModeParameters", {"conn_id": self._conn_id, "latency_mode": self._latency_mode, "auto_disconnect_time": self._auto_disconnect_time})
|
||||
self._client._send_command("CmdChangeModeParameters",
|
||||
{"conn_id": self._conn_id, "latency_mode": self._latency_mode,
|
||||
"auto_disconnect_time": self._auto_disconnect_time})
|
||||
|
||||
|
||||
class FlicClient(asyncio.Protocol):
|
||||
"""FlicClient class.
|
||||
|
@ -212,7 +228,8 @@ class FlicClient(asyncio.Protocol):
|
|||
("EvtButtonSingleOrDoubleClick", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||
("EvtButtonSingleOrDoubleClickOrHold", "<IBBI", "conn_id click_type was_queued time_diff"),
|
||||
("EvtNewVerifiedButton", "<6s", "bd_addr"),
|
||||
("EvtGetInfoResponse", "<B6sBBhBBH", "bluetooth_controller_state my_bd_addr my_bd_addr_type max_pending_connections max_concurrently_connected_buttons current_pending_connections currently_no_space_for_new_connection nb_verified_buttons"),
|
||||
("EvtGetInfoResponse", "<B6sBBhBBH",
|
||||
"bluetooth_controller_state my_bd_addr my_bd_addr_type max_pending_connections max_concurrently_connected_buttons current_pending_connections currently_no_space_for_new_connection nb_verified_buttons"),
|
||||
("EvtNoSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
||||
("EvtGotSpaceForNewConnection", "<B", "max_concurrently_connected_buttons"),
|
||||
("EvtBluetoothControllerStateChange", "<B", "state"),
|
||||
|
@ -223,8 +240,8 @@ class FlicClient(asyncio.Protocol):
|
|||
("EvtScanWizardButtonConnected", "<I", "scan_wizard_id"),
|
||||
("EvtScanWizardCompleted", "<IB", "scan_wizard_id result")
|
||||
]
|
||||
_EVENT_STRUCTS = list(map(lambda x: None if x == None else struct.Struct(x[1]), _EVENTS))
|
||||
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x == None else namedtuple(x[0], x[2]), _EVENTS))
|
||||
_EVENT_STRUCTS = list(map(lambda x: None if x is None else struct.Struct(x[1]), _EVENTS))
|
||||
_EVENT_NAMED_TUPLES = list(map(lambda x: None if x is None else namedtuple(x[0], x[2]), _EVENTS))
|
||||
|
||||
_COMMANDS = [
|
||||
("CmdGetInfo", "", ""),
|
||||
|
@ -244,18 +261,19 @@ class FlicClient(asyncio.Protocol):
|
|||
_COMMAND_NAMED_TUPLES = list(map(lambda x: namedtuple(x[0], x[2]), _COMMANDS))
|
||||
_COMMAND_NAME_TO_OPCODE = dict((x[0], i) for i, x in enumerate(_COMMANDS))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _bdaddr_bytes_to_string(bdaddr_bytes):
|
||||
return ":".join(map(lambda x: "%02x" % x, reversed(bdaddr_bytes)))
|
||||
|
||||
@staticmethod
|
||||
def _bdaddr_string_to_bytes(bdaddr_string):
|
||||
return bytearray.fromhex("".join(reversed(bdaddr_string.split(":"))))
|
||||
|
||||
def __init__(self, loop,parent=None):
|
||||
def __init__(self, loop, parent=None):
|
||||
self.loop = loop
|
||||
self.buffer=b""
|
||||
self.transport=None
|
||||
self.parent=parent
|
||||
self.buffer = b""
|
||||
self.transport = None
|
||||
self.parent = parent
|
||||
self._scanners = {}
|
||||
self._scan_wizards = {}
|
||||
self._connection_channels = {}
|
||||
|
@ -269,11 +287,10 @@ class FlicClient(asyncio.Protocol):
|
|||
self.on_get_button_uuid = lambda addr, uuid: None
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport=transport
|
||||
self.transport = transport
|
||||
if self.parent:
|
||||
self.parent.register_protocol(self)
|
||||
|
||||
|
||||
def close(self):
|
||||
"""Closes the client. The handle_events() method will return."""
|
||||
if self._closed:
|
||||
|
@ -342,7 +359,9 @@ class FlicClient(asyncio.Protocol):
|
|||
channel._client = self
|
||||
|
||||
self._connection_channels[channel._conn_id] = channel
|
||||
self._send_command("CmdCreateConnectionChannel", {"conn_id": channel._conn_id, "bd_addr": channel.bd_addr, "latency_mode": channel._latency_mode, "auto_disconnect_time": channel._auto_disconnect_time})
|
||||
self._send_command("CmdCreateConnectionChannel", {"conn_id": channel._conn_id, "bd_addr": channel.bd_addr,
|
||||
"latency_mode": channel._latency_mode,
|
||||
"auto_disconnect_time": channel._auto_disconnect_time})
|
||||
|
||||
def remove_connection_channel(self, channel):
|
||||
"""Remove a connection channel.
|
||||
|
@ -384,7 +403,6 @@ class FlicClient(asyncio.Protocol):
|
|||
"""
|
||||
self._send_command("CmdGetButtonUUID", {"bd_addr": bd_addr})
|
||||
|
||||
|
||||
def run_on_handle_events_thread(self, callback):
|
||||
"""Run a function on the thread that handles the events."""
|
||||
if threading.get_ident() == self._handle_event_thread_ident:
|
||||
|
@ -399,7 +417,7 @@ class FlicClient(asyncio.Protocol):
|
|||
items[key] = value.value
|
||||
|
||||
if "bd_addr" in items:
|
||||
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes(items["bd_addr"])
|
||||
items["bd_addr"] = FlicClient._bdaddr_string_to_bytes()
|
||||
|
||||
opcode = FlicClient._COMMAND_NAME_TO_OPCODE[name]
|
||||
data_bytes = FlicClient._COMMAND_STRUCTS[opcode].pack(*FlicClient._COMMAND_NAMED_TUPLES[opcode](**items))
|
||||
|
@ -415,16 +433,16 @@ class FlicClient(asyncio.Protocol):
|
|||
return
|
||||
opcode = data[0]
|
||||
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] == None:
|
||||
if opcode >= len(FlicClient._EVENTS) or FlicClient._EVENTS[opcode] is None:
|
||||
return
|
||||
|
||||
event_name = FlicClient._EVENTS[opcode][0]
|
||||
data_tuple = FlicClient._EVENT_STRUCTS[opcode].unpack(data[1 : 1 + FlicClient._EVENT_STRUCTS[opcode].size])
|
||||
data_tuple = FlicClient._EVENT_STRUCTS[opcode].unpack(data[1: 1 + FlicClient._EVENT_STRUCTS[opcode].size])
|
||||
items = FlicClient._EVENT_NAMED_TUPLES[opcode]._make(data_tuple)._asdict()
|
||||
|
||||
# Process some kind of items whose data type is not supported by struct
|
||||
if "bd_addr" in items:
|
||||
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["bd_addr"])
|
||||
items["bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||
|
||||
if "name" in items:
|
||||
items["name"] = items["name"].decode("utf-8")
|
||||
|
@ -445,13 +463,14 @@ class FlicClient(asyncio.Protocol):
|
|||
|
||||
if event_name == "EvtGetInfoResponse":
|
||||
items["bluetooth_controller_state"] = BluetoothControllerState(items["bluetooth_controller_state"])
|
||||
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string(items["my_bd_addr"])
|
||||
items["my_bd_addr"] = FlicClient._bdaddr_bytes_to_string()
|
||||
items["my_bd_addr_type"] = BdAddrType(items["my_bd_addr_type"])
|
||||
items["bd_addr_of_verified_buttons"] = []
|
||||
|
||||
pos = FlicClient._EVENT_STRUCTS[opcode].size
|
||||
for i in range(items["nb_verified_buttons"]):
|
||||
items["bd_addr_of_verified_buttons"].append(FlicClient._bdaddr_bytes_to_string(data[1 + pos : 1 + pos + 6]))
|
||||
items["bd_addr_of_verified_buttons"].append(
|
||||
FlicClient._bdaddr_bytes_to_string())
|
||||
pos += 6
|
||||
|
||||
if event_name == "EvtBluetoothControllerStateChange":
|
||||
|
@ -469,7 +488,8 @@ class FlicClient(asyncio.Protocol):
|
|||
if event_name == "EvtAdvertisementPacket":
|
||||
scanner = self._scanners.get(items["scan_id"])
|
||||
if scanner is not None:
|
||||
scanner.on_advertisement_packet(scanner, items["bd_addr"], items["name"], items["rssi"], items["is_private"], items["already_verified"])
|
||||
scanner.on_advertisement_packet(scanner, items["bd_addr"], items["name"], items["rssi"],
|
||||
items["is_private"], items["already_verified"])
|
||||
|
||||
if event_name == "EvtCreateConnectionChannelResponse":
|
||||
channel = self._connection_channels[items["conn_id"]]
|
||||
|
@ -494,10 +514,12 @@ class FlicClient(asyncio.Protocol):
|
|||
channel.on_button_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
||||
if event_name == "EvtButtonSingleOrDoubleClick":
|
||||
channel = self._connection_channels[items["conn_id"]]
|
||||
channel.on_button_single_or_double_click(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
||||
channel.on_button_single_or_double_click(channel, items["click_type"], items["was_queued"],
|
||||
items["time_diff"])
|
||||
if event_name == "EvtButtonSingleOrDoubleClickOrHold":
|
||||
channel = self._connection_channels[items["conn_id"]]
|
||||
channel.on_button_single_or_double_click_or_hold(channel, items["click_type"], items["was_queued"], items["time_diff"])
|
||||
channel.on_button_single_or_double_click_or_hold(channel, items["click_type"], items["was_queued"],
|
||||
items["time_diff"])
|
||||
|
||||
if event_name == "EvtNewVerifiedButton":
|
||||
self.on_new_verified_button(items["bd_addr"])
|
||||
|
@ -536,19 +558,16 @@ class FlicClient(asyncio.Protocol):
|
|||
del self._scan_wizards[items["scan_wizard_id"]]
|
||||
scan_wizard.on_completed(scan_wizard, items["result"], scan_wizard._bd_addr, scan_wizard._name)
|
||||
|
||||
|
||||
def data_received(self,data):
|
||||
cdata=self.buffer+data
|
||||
self.buffer=b""
|
||||
def data_received(self, data):
|
||||
cdata = self.buffer + data
|
||||
self.buffer = b""
|
||||
while len(cdata):
|
||||
packet_len = cdata[0] | (cdata[1] << 8)
|
||||
packet_len += 2
|
||||
if len(cdata)>= packet_len:
|
||||
if len(cdata) >= packet_len:
|
||||
self._dispatch_event(cdata[2:packet_len])
|
||||
cdata=cdata[packet_len:]
|
||||
cdata = cdata[packet_len:]
|
||||
else:
|
||||
if len(cdata):
|
||||
self.buffer=cdata #unlikely to happen but.....
|
||||
self.buffer = cdata # unlikely to happen but.....
|
||||
break
|
||||
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class CameraPiBackend(Backend):
|
|||
return self.value == other
|
||||
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
def __init__(self, listen_port, x_resolution=640, y_resolution=480,
|
||||
def __init__(self, listen_port, bind_address='0.0.0.0', x_resolution=640, y_resolution=480,
|
||||
redis_queue='platypush/camera/pi',
|
||||
start_recording_on_startup=True,
|
||||
framerate=24, hflip=False, vflip=False,
|
||||
|
@ -49,13 +49,17 @@ class CameraPiBackend(Backend):
|
|||
|
||||
:param listen_port: Port where the camera process will provide the video output while recording
|
||||
:type listen_port: int
|
||||
|
||||
:param bind_address: Bind address (default: 0.0.0.0).
|
||||
:type bind_address: str
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bind_address = bind_address
|
||||
self.listen_port = listen_port
|
||||
self.server_socket = socket.socket()
|
||||
self.server_socket.bind(('0.0.0.0', self.listen_port))
|
||||
self.server_socket.bind((self.bind_address, self.listen_port))
|
||||
self.server_socket.listen(0)
|
||||
|
||||
import picamera
|
||||
|
@ -134,13 +138,13 @@ class CameraPiBackend(Backend):
|
|||
self.logger.info('Client closed connection')
|
||||
try:
|
||||
self.stop_recording()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not stop recording: {}'.format(str(e)))
|
||||
|
||||
try:
|
||||
connection.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close connection: {}'.format(str(e)))
|
||||
|
||||
self.send_camera_action(self.CameraAction.START_RECORDING)
|
||||
|
||||
|
|
|
@ -51,11 +51,10 @@ class GooglePubsubBackend(Backend):
|
|||
def _message_callback(self, topic):
|
||||
def callback(msg):
|
||||
data = msg.data.decode()
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug('Not a valid JSON: {}: {}'.format(data, str(e)))
|
||||
|
||||
msg.ack()
|
||||
self.bus.post(GooglePubsubMessageEvent(topic=topic, msg=data))
|
||||
|
|
|
@ -31,8 +31,8 @@ def _hook(hook_name):
|
|||
# noinspection PyBroadException
|
||||
try:
|
||||
event_args['data'] = json.loads(event_args['data'])
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger().warning('Not a valid JSON string: {}: {}'.format(event_args['data'], str(e)))
|
||||
|
||||
event = WebhookEvent(**event_args)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ def login():
|
|||
if session_token:
|
||||
user, session = user_manager.authenticate_user_session(session_token)
|
||||
if user:
|
||||
return redirect(redirect_page, 302)
|
||||
return redirect(redirect_page, 302) # lgtm [py/url-redirection]
|
||||
|
||||
if request.form:
|
||||
username = request.form.get('username')
|
||||
|
@ -44,7 +44,7 @@ def login():
|
|||
expires_at=expires)
|
||||
|
||||
if session:
|
||||
redirect_target = redirect(redirect_page, 302)
|
||||
redirect_target = redirect(redirect_page, 302) # lgtm [py/url-redirection]
|
||||
response = make_response(redirect_target)
|
||||
response.set_cookie('session_token', session.session_token, expires=expires)
|
||||
return response
|
||||
|
|
|
@ -25,7 +25,7 @@ def logout():
|
|||
if not user:
|
||||
return abort(403, 'Invalid session token')
|
||||
|
||||
redirect_target = redirect(redirect_page, 302)
|
||||
redirect_target = redirect(redirect_page, 302) # lgtm [py/url-redirection]
|
||||
response = make_response(redirect_target)
|
||||
response.set_cookie('session_token', '', expires=0)
|
||||
return response
|
||||
|
|
|
@ -32,8 +32,8 @@ def add_media():
|
|||
args = {}
|
||||
try:
|
||||
args = json.loads(request.data.decode('utf-8'))
|
||||
except:
|
||||
abort(400, 'Invalid JSON request')
|
||||
except Exception as e:
|
||||
abort(400, 'Invalid JSON request: {}'.format(str(e)))
|
||||
|
||||
source = args.get('source')
|
||||
if not source:
|
||||
|
|
|
@ -40,7 +40,7 @@ def audio_feed(device, fifo, sample_rate, blocksize, latency, channels):
|
|||
channels=channels)
|
||||
|
||||
try:
|
||||
with open(fifo, 'rb') as f:
|
||||
with open(fifo, 'rb') as f: # lgtm [py/path-injection]
|
||||
send_header = True
|
||||
|
||||
while True:
|
||||
|
|
|
@ -31,10 +31,10 @@ def register():
|
|||
if session_token:
|
||||
user, session = user_manager.authenticate_user_session(session_token)
|
||||
if user:
|
||||
return redirect(redirect_page, 302)
|
||||
return redirect(redirect_page, 302) # lgtm [py/url-redirection]
|
||||
|
||||
if user_manager.get_user_count() > 0:
|
||||
return redirect('/login?redirect=' + redirect_page, 302)
|
||||
return redirect('/login?redirect=' + redirect_page, 302) # lgtm [py/url-redirection]
|
||||
|
||||
if request.form:
|
||||
username = request.form.get('username')
|
||||
|
@ -49,7 +49,7 @@ def register():
|
|||
if not remember else None)
|
||||
|
||||
if session:
|
||||
redirect_target = redirect(redirect_page, 302)
|
||||
redirect_target = redirect(redirect_page, 302) # lgtm [py/url-redirection]
|
||||
response = make_response(redirect_target)
|
||||
response.set_cookie('session_token', session.session_token)
|
||||
return response
|
||||
|
|
|
@ -123,7 +123,8 @@ def _authenticate_token():
|
|||
try:
|
||||
user_manager.validate_jwt_token(user_token)
|
||||
return True
|
||||
except:
|
||||
except Exception as e:
|
||||
logger().debug(str(e))
|
||||
return token and user_token == token
|
||||
|
||||
|
||||
|
|
|
@ -78,11 +78,11 @@ class HttpRequest(object):
|
|||
|
||||
def get_new_items(self, response):
|
||||
""" Gets new items out of a response """
|
||||
raise ("get_new_items must be implemented in a derived class")
|
||||
raise NotImplementedError("get_new_items must be implemented in a derived class")
|
||||
|
||||
def __iter__(self):
|
||||
for (key, value) in self.request_args.items():
|
||||
yield (key, value)
|
||||
yield key, value
|
||||
|
||||
|
||||
class JsonHttpRequest(HttpRequest):
|
||||
|
@ -96,7 +96,7 @@ class JsonHttpRequest(HttpRequest):
|
|||
new_entries = []
|
||||
|
||||
if self.path:
|
||||
m = re.match('\$\{\s*(.*)\s*\}', self.path)
|
||||
m = re.match(r'\${\s*(.*)\s*}', self.path)
|
||||
response = eval(m.group(1))
|
||||
|
||||
for entry in response:
|
||||
|
|
|
@ -238,15 +238,15 @@ class RssUpdates(HttpRequest):
|
|||
with open(digest_filename, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
elif self.digest_format == 'pdf':
|
||||
import weasyprint
|
||||
from weasyprint import HTML, CSS
|
||||
from weasyprint.fonts import FontConfiguration
|
||||
|
||||
body_style = 'body { {body_style} }'.format(body_style=self.body_style)
|
||||
body_style = 'body { ' + self.body_style + ' }'
|
||||
font_config = FontConfiguration()
|
||||
css = [weasyprint.CSS('https://fonts.googleapis.com/css?family=Merriweather'),
|
||||
weasyprint.CSS(string=body_style, font_config=font_config)]
|
||||
css = [CSS('https://fonts.googleapis.com/css?family=Merriweather'),
|
||||
CSS(string=body_style, font_config=font_config)]
|
||||
|
||||
weasyprint.HTML(string=content).write_pdf(digest_filename, stylesheets=css)
|
||||
HTML(string=content).write_pdf(digest_filename, stylesheets=css)
|
||||
else:
|
||||
raise RuntimeError('Unsupported format: {}. Supported formats: ' +
|
||||
'html or pdf'.format(self.digest_format))
|
||||
|
|
|
@ -50,8 +50,6 @@ class HttpUtils(object):
|
|||
for name, resource_path in resource_dirs.items():
|
||||
resource_path = os.path.abspath(os.path.expanduser(resource_path))
|
||||
if directory.startswith(resource_path):
|
||||
subdir = re.sub('^{}(.*)$'.format(resource_path),
|
||||
'\\1', directory)
|
||||
uri = '/resources/' + name
|
||||
break
|
||||
|
||||
|
@ -92,10 +90,11 @@ class HttpUtils(object):
|
|||
|
||||
@classmethod
|
||||
def plugin_name_to_tag(cls, module_name):
|
||||
return module_name.replace('.','-')
|
||||
return module_name.replace('.', '-')
|
||||
|
||||
@classmethod
|
||||
def find_templates_in_dir(cls, directory):
|
||||
# noinspection PyTypeChecker
|
||||
return [
|
||||
os.path.join(directory, file)
|
||||
for root, path, files in os.walk(os.path.abspath(os.path.join(template_folder, directory)))
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/chunk-178b19d7.1c7265c0.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-178b19d7.1c7265c0.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/chunk-1ad96db7.02188e12.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-1ad96db7.02188e12.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/chunk-6c14c2d1.94636887.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/chunk-6c14c2d1.94636887.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/chunk-6c14c2d1.94636887.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-6c14c2d1.94636887.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -380,20 +380,16 @@ export default {
|
|||
|
||||
this.request(this.action.name, args).then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
} else {
|
||||
let request = this.rawRequest
|
||||
try {
|
||||
request = JSON.parse(this.rawRequest)
|
||||
const request = JSON.parse(this.rawRequest)
|
||||
this.execute(request).then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
} catch (e) {
|
||||
this.notify({
|
||||
error: true,
|
||||
title: 'Invalid JSON request',
|
||||
text: e.toString(),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.execute(request).then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ export class ColorConverter {
|
|||
if (isNaN(blue))
|
||||
blue = 0;
|
||||
|
||||
// lgtm [js/automatic-semicolon-insertion]
|
||||
return [red, green, blue].map((c) => Math.min(Math.max(0, c), 255))
|
||||
}
|
||||
|
||||
|
|
|
@ -454,9 +454,10 @@ export default {
|
|||
brightness: hsl[2],
|
||||
color: {
|
||||
hue: hsl[0],
|
||||
'`${satAttr}': hsl[1],
|
||||
}
|
||||
}
|
||||
|
||||
request.value.color[satAttr] = hsl[1]
|
||||
}
|
||||
}
|
||||
break
|
||||
|
|
|
@ -35,6 +35,7 @@ class KafkaBackend(Backend):
|
|||
self.topic_prefix = topic
|
||||
self.topic = self._topic_by_device_id(self.device_id)
|
||||
self.producer = None
|
||||
self.consumer = None
|
||||
|
||||
# Kafka can be veryyyy noisy
|
||||
logging.getLogger('kafka').setLevel(logging.ERROR)
|
||||
|
@ -47,8 +48,8 @@ class KafkaBackend(Backend):
|
|||
try:
|
||||
msg = Message.build(msg)
|
||||
is_platypush_message = True
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(str(e))
|
||||
|
||||
self.logger.info('Received message on Kafka backend: {}'.format(msg))
|
||||
|
||||
|
@ -60,7 +61,7 @@ class KafkaBackend(Backend):
|
|||
def _topic_by_device_id(self, device_id):
|
||||
return '{}.{}'.format(self.topic_prefix, device_id)
|
||||
|
||||
def send_message(self, msg):
|
||||
def send_message(self, msg, **kwargs):
|
||||
target = msg.target
|
||||
kafka_plugin = get_plugin('kafka')
|
||||
kafka_plugin.send_message(msg=msg,
|
||||
|
|
|
@ -108,8 +108,8 @@ class LogEventHandler(EventHandler):
|
|||
url = m.group(5).split(' ')[1]
|
||||
method = m.group(5).split(' ')[0]
|
||||
http_version = m.group(5).split(' ')[2].split('/')[1]
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(str(e))
|
||||
|
||||
if not url:
|
||||
return
|
||||
|
|
|
@ -275,8 +275,8 @@ class MqttBackend(Backend):
|
|||
try:
|
||||
data = data.decode('utf-8')
|
||||
data = json.loads(data)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(str(e))
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
self.bus.post(MQTTMessageEvent(host=client._host, port=client._port, topic=msg.topic, msg=data))
|
||||
|
@ -303,8 +303,8 @@ class MqttBackend(Backend):
|
|||
try:
|
||||
msg = json.loads(msg)
|
||||
msg = Message.build(msg)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(str(e))
|
||||
|
||||
if not msg:
|
||||
return
|
||||
|
|
|
@ -49,10 +49,10 @@ class PushbulletBackend(Backend):
|
|||
from pushbullet import Pushbullet
|
||||
self.pb = Pushbullet(self.token)
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self.device = self.pb.get_device(self.device_name)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.info('Device {} does not exist: {}. Creating it'.format(self.device_name, str(e)))
|
||||
self.device = self.pb.new_device(self.device_name)
|
||||
|
||||
self.pb_device_id = self.get_device_id()
|
||||
|
|
|
@ -60,15 +60,17 @@ class RedisBackend(Backend):
|
|||
msg = data[1].decode()
|
||||
try:
|
||||
msg = Message.build(msg)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(str(e))
|
||||
try:
|
||||
import ast
|
||||
msg = Message.build(ast.literal_eval(msg))
|
||||
except:
|
||||
except Exception as ee:
|
||||
self.logger.debug(str(ee))
|
||||
try:
|
||||
msg = json.loads(msg)
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
except Exception as eee:
|
||||
self.logger.exception(eee)
|
||||
|
||||
return msg
|
||||
|
||||
|
|
|
@ -119,12 +119,11 @@ class SensorBackend(Backend):
|
|||
if self.data is None or new_data is None:
|
||||
return new_data
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# Scalar data case
|
||||
new_data = self._get_value(new_data)
|
||||
return new_data if abs(new_data - self.data) >= self.tolerance else None
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
# If it's not a scalar then it should be a dict
|
||||
assert isinstance(new_data, dict), 'Invalid type {} received for sensor data'.format(type(new_data))
|
||||
|
||||
|
|
|
@ -6,12 +6,9 @@ import re
|
|||
import time
|
||||
|
||||
from platypush.backend import Backend
|
||||
from platypush.message import Message
|
||||
from platypush.message.event.wiimote import WiimoteEvent, \
|
||||
WiimoteConnectionEvent, WiimoteDisconnectionEvent
|
||||
|
||||
from platypush.message.request import Request
|
||||
|
||||
|
||||
class WiimoteBackend(Backend):
|
||||
"""
|
||||
|
@ -110,8 +107,8 @@ class WiimoteBackend(Backend):
|
|||
|
||||
try:
|
||||
self._wiimote.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close Wiimote connection: {}'.format(str(e)))
|
||||
|
||||
self._wiimote = None
|
||||
self.bus.post(WiimoteDisconnectionEvent())
|
||||
|
|
|
@ -232,10 +232,9 @@ class ZigbeeMqttBackend(MqttBackend):
|
|||
if not data:
|
||||
return
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if topic == 'bridge/state':
|
||||
|
|
|
@ -57,7 +57,7 @@ class Cronjob(threading.Thread):
|
|||
self.state = CronjobState.ERROR
|
||||
|
||||
def wait(self):
|
||||
now = datetime.datetime.now().replace(tzinfo=gettz())
|
||||
now = datetime.datetime.now().replace(tzinfo=gettz()) # lgtm [py/call-to-non-callable]
|
||||
cron = croniter.croniter(self.cron_expression, now)
|
||||
next_run = cron.get_next()
|
||||
self._should_stop.wait(next_run - now.timestamp())
|
||||
|
|
|
@ -25,8 +25,8 @@ class EventGenerator(object):
|
|||
:type event: :class:`platypush.message.event.Event` or a subclass
|
||||
"""
|
||||
|
||||
def hndl_thread():
|
||||
hndl(event)
|
||||
def hndl_thread(handler):
|
||||
handler(event)
|
||||
|
||||
from platypush.backend import Backend
|
||||
from platypush.context import get_bus
|
||||
|
@ -44,8 +44,7 @@ class EventGenerator(object):
|
|||
handlers.update(self._event_handlers[cls])
|
||||
|
||||
for hndl in handlers:
|
||||
threading.Thread(target=hndl_thread).start()
|
||||
|
||||
threading.Thread(target=hndl_thread, args=(hndl,)).start()
|
||||
|
||||
def register_handler(self, event_type, callback):
|
||||
"""
|
||||
|
@ -64,7 +63,6 @@ class EventGenerator(object):
|
|||
self._event_handlers[event_type] = set()
|
||||
self._event_handlers[event_type].add(callback)
|
||||
|
||||
|
||||
def unregister_handler(self, event_type, callback):
|
||||
"""
|
||||
Unregisters a callback handler from a camera event type.
|
||||
|
|
|
@ -61,7 +61,8 @@ class EventProcessor(object):
|
|||
|
||||
if hook.priority > max_priority:
|
||||
priority_hooks = {hook}
|
||||
elif hook.priority == max_priority and max_priority > 0:
|
||||
max_priority = hook.priority
|
||||
elif hook.priority == max_priority:
|
||||
priority_hooks.add(hook)
|
||||
|
||||
matched_hooks.update(priority_hooks)
|
||||
|
|
|
@ -106,10 +106,9 @@ class Message(object):
|
|||
if isinstance(msg, bytes) or isinstance(msg, bytearray):
|
||||
msg = msg.decode('utf-8')
|
||||
if isinstance(msg, str):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
msg = json.loads(msg.strip())
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
logger.warning('Invalid JSON message: {}'.format(msg))
|
||||
|
||||
assert isinstance(msg, dict)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import json
|
||||
|
||||
from platypush.message.event import Event
|
||||
|
||||
|
||||
|
|
|
@ -119,7 +119,6 @@ class Request(Message):
|
|||
|
||||
return event_args
|
||||
|
||||
# noinspection PyBroadException
|
||||
@classmethod
|
||||
def expand_value_from_context(cls, _value, **context):
|
||||
for (k, v) in context.items():
|
||||
|
@ -127,7 +126,8 @@ class Request(Message):
|
|||
v = json.loads(str(v))
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.debug(str(e))
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
||||
|
@ -171,7 +171,7 @@ class Request(Message):
|
|||
|
||||
try:
|
||||
return json.loads(parsed_value)
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
return parsed_value
|
||||
|
||||
def _send_response(self, response):
|
||||
|
|
|
@ -40,10 +40,9 @@ class RectModel(Mapping):
|
|||
class ResultModel(Mapping):
|
||||
def __init__(self, result: Decoded, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
data = result.data.decode()
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
data = base64.encodebytes(result.data).decode()
|
||||
|
||||
self.data = data
|
||||
|
|
|
@ -142,7 +142,7 @@ class AssistantGooglePushtotalkPlugin(AssistantPlugin):
|
|||
)
|
||||
)
|
||||
|
||||
audio_sink = audio_device = (
|
||||
audio_sink = (
|
||||
audio_device or audio_helpers.SoundDeviceStream(
|
||||
sample_rate=self.audio_sample_rate,
|
||||
sample_width=self.audio_sample_width,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
from platypush.config import Config
|
||||
from platypush.message import Message
|
||||
from platypush.plugins import Plugin, action
|
||||
|
||||
|
||||
|
@ -22,7 +20,7 @@ class AutoremotePlugin(Plugin):
|
|||
_AUTOREMOTE_MESSAGE_URL = _AUTOREMOTE_BASE_URL + '/sendmessage'
|
||||
_AUTOREMOTE_NOTIFICATION_URL = _AUTOREMOTE_BASE_URL + '/sendnotification'
|
||||
|
||||
def __init__(self, devices=None, key=None, password=None, *args, **kwargs):
|
||||
def __init__(self, devices=None, key=None, password=None, **kwargs):
|
||||
"""
|
||||
:param devices: Set this attribute if you want to control multiple AutoRemote devices.
|
||||
This will be a map in the format::
|
||||
|
@ -47,7 +45,7 @@ class AutoremotePlugin(Plugin):
|
|||
:type password: str
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if key:
|
||||
self.devices = { key: { 'key': key, 'password': password } }
|
||||
|
|
|
@ -404,11 +404,10 @@ class CameraPlugin(Plugin, ABC):
|
|||
frame_queue.put(None)
|
||||
self.stop_preview(camera)
|
||||
for output in camera.get_outputs():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
output.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close camera output: {}'.format(str(e)))
|
||||
|
||||
self.close_device(camera, wait_capture=False)
|
||||
frame_processor.join(timeout=5.0)
|
||||
|
|
|
@ -114,11 +114,10 @@ class StreamWriter(VideoWriter, ABC):
|
|||
def close(self):
|
||||
self.buffer.close()
|
||||
if self.sock:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self.sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close camera resource: {}'.format(str(e)))
|
||||
|
||||
super().close()
|
||||
|
||||
|
|
|
@ -24,12 +24,11 @@ class CvFileWriter(FileVideoWriter):
|
|||
if not self.writer:
|
||||
return
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
if isinstance(img, ImageType):
|
||||
# noinspection PyTypeChecker
|
||||
img = np.array(img)
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
self.writer.write(img)
|
||||
|
|
|
@ -78,7 +78,7 @@ class DbPlugin(Plugin):
|
|||
engine = self._get_engine(engine, *args, **kwargs)
|
||||
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(statement)
|
||||
connection.execute(statement)
|
||||
|
||||
def _get_table(self, table, engine=None, *args, **kwargs):
|
||||
if not engine:
|
||||
|
|
|
@ -188,11 +188,10 @@ class EspPlugin(Plugin):
|
|||
|
||||
def on_close(self, conn: Connection):
|
||||
def callback(ws):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
ws.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close connection: {}'.format(str(e)))
|
||||
|
||||
conn.on_close()
|
||||
self.logger.info('Connection to {}:{} closed'.format(conn.host, conn.port))
|
||||
|
@ -608,7 +607,8 @@ if {miso}:
|
|||
self.execute(code, **kwargs)
|
||||
|
||||
@action
|
||||
def i2c_open(self, scl: Optional[int] = None, sda: Optional[int] = None, id: int = -1, baudrate: int = 400000, **kwargs):
|
||||
def i2c_open(self, scl: Optional[int] = None, sda: Optional[int] = None, id: int = -1, baudrate: int = 400000,
|
||||
**kwargs):
|
||||
"""
|
||||
Open a connection to an I2C (or "two-wire") port.
|
||||
|
||||
|
|
|
@ -150,11 +150,10 @@ class Connection:
|
|||
self._echo_received.set()
|
||||
|
||||
def close(self):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self.ws.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not close connection: {}'.format(str(e)))
|
||||
|
||||
self.on_close()
|
||||
|
||||
|
@ -243,6 +242,7 @@ class Connection:
|
|||
chunk = self._downloaded_chunks.get(timeout=timeout)
|
||||
except queue.Empty:
|
||||
self.on_timeout('File download timed out')
|
||||
break
|
||||
|
||||
if chunk is None:
|
||||
break
|
||||
|
|
|
@ -19,7 +19,7 @@ class FilePlugin(Plugin):
|
|||
def _to_string(cls, content):
|
||||
try:
|
||||
return json.dumps(content)
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
return str(content)
|
||||
|
||||
@action
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||
"""
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.google import GooglePlugin
|
||||
|
@ -21,7 +19,6 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(scopes=self.scopes, *args, **kwargs)
|
||||
|
||||
|
||||
@action
|
||||
def get_upcoming_events(self, max_results=10):
|
||||
"""
|
||||
|
@ -40,4 +37,3 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
|
|||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ def generate_credentials(client_secret_path, scope):
|
|||
flow.user_agent = 'Platypush'
|
||||
flow.access_type = 'offline'
|
||||
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
|
||||
credentials = tools.run_flow(flow, store, flags)
|
||||
tools.run_flow(flow, store, flags)
|
||||
print('Storing credentials to ' + credentials_file)
|
||||
|
||||
|
||||
|
@ -76,6 +76,4 @@ def main():
|
|||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"""
|
||||
|
||||
import base64
|
||||
import httplib2
|
||||
import mimetypes
|
||||
import os
|
||||
|
||||
|
@ -29,7 +28,6 @@ class GoogleMailPlugin(GooglePlugin):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(scopes=self.scopes, *args, **kwargs)
|
||||
|
||||
|
||||
@action
|
||||
def compose(self, sender, to, subject, body, files=None):
|
||||
"""
|
||||
|
@ -66,9 +64,11 @@ class GoogleMailPlugin(GooglePlugin):
|
|||
content_type = 'application/octet-stream'
|
||||
|
||||
main_type, sub_type = content_type.split('/', 1)
|
||||
with open(file, 'rb') as fp: content = fp.read()
|
||||
with open(file, 'rb') as fp:
|
||||
content = fp.read()
|
||||
|
||||
if main_type == 'text':
|
||||
# noinspection PyUnresolvedReferences
|
||||
msg = mimetypes.MIMEText(content, _subtype=sub_type)
|
||||
elif main_type == 'image':
|
||||
msg = MIMEImage(content, _subtype=sub_type)
|
||||
|
@ -86,13 +86,12 @@ class GoogleMailPlugin(GooglePlugin):
|
|||
message.attach(msg)
|
||||
|
||||
service = self.get_service('gmail', 'v1')
|
||||
body = { 'raw': base64.urlsafe_b64encode(message.as_bytes()).decode() }
|
||||
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
|
||||
message = (service.users().messages().send(
|
||||
userId='me', body=body).execute())
|
||||
|
||||
return message
|
||||
|
||||
|
||||
@action
|
||||
def get_labels(self):
|
||||
"""
|
||||
|
@ -103,5 +102,4 @@ class GoogleMailPlugin(GooglePlugin):
|
|||
labels = results.get('labels', [])
|
||||
return labels
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
.. moduleauthor:: Fabio Manganiello <blacklight86@gmail.com>
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from platypush.plugins import action
|
||||
|
@ -18,14 +17,14 @@ class GoogleMapsPlugin(GooglePlugin):
|
|||
|
||||
def __init__(self, api_key, *args, **kwargs):
|
||||
"""
|
||||
:param api_key: Server-side API key to be used for the requests, get one at https://console.developers.google.com
|
||||
:param api_key: Server-side API key to be used for the requests, get one at
|
||||
https://console.developers.google.com
|
||||
:type api_key: str
|
||||
"""
|
||||
|
||||
super().__init__(scopes=self.scopes, *args, **kwargs)
|
||||
self.api_key = api_key
|
||||
|
||||
|
||||
@action
|
||||
def get_address_from_latlng(self, latitude, longitude):
|
||||
"""
|
||||
|
@ -39,7 +38,7 @@ class GoogleMapsPlugin(GooglePlugin):
|
|||
"""
|
||||
|
||||
response = requests.get('https://maps.googleapis.com/maps/api/geocode/json',
|
||||
params = {
|
||||
params={
|
||||
'latlng': '{},{}'.format(latitude, longitude),
|
||||
'key': self.api_key,
|
||||
}).json()
|
||||
|
@ -81,7 +80,7 @@ class GoogleMapsPlugin(GooglePlugin):
|
|||
"""
|
||||
|
||||
response = requests.get('https://maps.googleapis.com/maps/api/elevation/json',
|
||||
params = {
|
||||
params={
|
||||
'locations': '{},{}'.format(latitude, longitude),
|
||||
'key': self.api_key,
|
||||
}).json()
|
||||
|
@ -91,8 +90,6 @@ class GoogleMapsPlugin(GooglePlugin):
|
|||
if response.get('results'):
|
||||
elevation = response['results'][0]['elevation']
|
||||
|
||||
return { 'elevation': elevation }
|
||||
|
||||
return {'elevation': elevation}
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
|
@ -78,8 +78,8 @@ class GpioSensorDistanceVl53L1XPlugin(GpioSensorPlugin):
|
|||
try:
|
||||
self._device.stop_ranging()
|
||||
self._device.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error while closing device: {}'.format(str(e)))
|
||||
|
||||
self._device = None
|
||||
time.sleep(1)
|
||||
|
|
|
@ -12,7 +12,7 @@ class MCP3008Mode(enum.Enum):
|
|||
class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
||||
"""
|
||||
Plugin to read analog sensor values from an MCP3008 chipset. The MCP3008
|
||||
chipset is a circuit that allows you to read measuremnts from multiple
|
||||
chipset is a circuit that allows you to read measurements from multiple
|
||||
analog sources (e.g. sensors) and multiplex them to a digital device like a
|
||||
Raspberry Pi or a regular laptop. See
|
||||
https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008
|
||||
|
@ -25,8 +25,9 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
|
||||
N_CHANNELS = 8
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
def __init__(self, CLK=None, MISO=None, MOSI=None, CS=None, spi_port=None,
|
||||
spi_device=None, channels=None, Vdd=3.3, *args, **kwargs):
|
||||
spi_device=None, channels=None, Vdd=3.3, **kwargs):
|
||||
"""
|
||||
The MCP3008 can be connected in two modes:
|
||||
|
||||
|
@ -54,8 +55,8 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
:param spi_device: (hardware SPI mode) SPI device name
|
||||
:type spi_device: str
|
||||
|
||||
:param channels: name-value mapping between MCP3008 output PINs and sensor names. This mapping will be used when you get values through :func:`.get_measurement()`.
|
||||
Example::
|
||||
:param channels: name-value mapping between MCP3008 output PINs and sensor names. This mapping will be used
|
||||
when you get values through :func:`.get_measurement()`. Example::
|
||||
|
||||
channels = {
|
||||
"0": {
|
||||
|
@ -83,7 +84,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
:type Vdd: float
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if CLK and MISO and MOSI and CS:
|
||||
self.CLK = CLK
|
||||
|
@ -154,6 +155,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
if i in self.channels:
|
||||
channel = self.channels[i]
|
||||
if 'conv_function' in channel:
|
||||
# noinspection PyUnusedLocal
|
||||
x = value
|
||||
value = eval(channel['conv_function'])
|
||||
|
||||
|
|
|
@ -164,8 +164,9 @@ Warning, this new I?C address will still be used after resetting the power on th
|
|||
print('Missing ZeroBorg at %02X' % oldAddress)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
except Exception as e:
|
||||
foundChip = False
|
||||
print(str(e))
|
||||
print('Missing ZeroBorg at %02X' % oldAddress)
|
||||
if foundChip:
|
||||
bus.RawWrite(COMMAND_SET_I2C_ADD, [newAddress])
|
||||
|
@ -188,8 +189,9 @@ Warning, this new I?C address will still be used after resetting the power on th
|
|||
print('Missing ZeroBorg at %02X' % newAddress)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
except Exception as e:
|
||||
foundChip = False
|
||||
print(str(e))
|
||||
print('Missing ZeroBorg at %02X' % newAddress)
|
||||
if foundChip:
|
||||
print('New I?C address of %02X set successfully' % newAddress)
|
||||
|
@ -332,9 +334,9 @@ the current busNumber.
|
|||
self.Print('Missing ZeroBorg at %02X' % self.i2cAddress)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
except Exception as e:
|
||||
self.foundChip = False
|
||||
self.Print('Missing ZeroBorg at %02X' % self.i2cAddress)
|
||||
self.Print('Missing ZeroBorg at %02X: %s' % (self.i2cAddress, str(e)))
|
||||
|
||||
# See if we are missing chips
|
||||
if not self.foundChip:
|
||||
|
@ -383,8 +385,8 @@ SetMotor1(1) -> motor 1 moving forward at 100% power
|
|||
self.RawWrite(command, [pwm])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending motor 1 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending motor 1 drive level! {}'.format(str(e)))
|
||||
|
||||
def GetMotor1(self):
|
||||
"""
|
||||
|
@ -402,8 +404,8 @@ e.g.
|
|||
i2cRecv = self.RawRead(COMMAND_GET_A, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading motor 1 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading motor 1 drive level! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
power = float(i2cRecv[2]) / float(PWM_MAX)
|
||||
|
@ -444,8 +446,8 @@ SetMotor2(1) -> motor 2 moving forward at 100% power
|
|||
self.RawWrite(command, [pwm])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending motor 2 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending motor 2 drive level! {}'.format(str(e)))
|
||||
|
||||
def GetMotor2(self):
|
||||
"""
|
||||
|
@ -463,8 +465,8 @@ e.g.
|
|||
i2cRecv = self.RawRead(COMMAND_GET_B, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading motor 2 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading motor 2 drive level! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
power = float(i2cRecv[2]) / float(PWM_MAX)
|
||||
|
@ -505,8 +507,8 @@ SetMotor3(1) -> motor 3 moving forward at 100% power
|
|||
self.RawWrite(command, [pwm])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending motor 3 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending motor 3 drive level! {}'.format(str(e)))
|
||||
|
||||
def GetMotor3(self):
|
||||
"""
|
||||
|
@ -524,8 +526,8 @@ e.g.
|
|||
i2cRecv = self.RawRead(COMMAND_GET_C, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading motor 3 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading motor 3 drive level! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
power = float(i2cRecv[2]) / float(PWM_MAX)
|
||||
|
@ -566,8 +568,8 @@ SetMotor4(1) -> motor 4 moving forward at 100% power
|
|||
self.RawWrite(command, [pwm])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending motor 4 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending motor 4 drive level! {}'.format(str(e)))
|
||||
|
||||
def GetMotor4(self):
|
||||
"""
|
||||
|
@ -585,8 +587,8 @@ e.g.
|
|||
i2cRecv = self.RawRead(COMMAND_GET_D, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading motor 4 drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading motor 4 drive level! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
power = float(i2cRecv[2]) / float(PWM_MAX)
|
||||
|
@ -627,8 +629,8 @@ SetMotors(1) -> all motors are moving forward at 100% power
|
|||
self.RawWrite(command, [pwm])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending all motors drive level!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending all motors drive level! {}'.format(str(e)))
|
||||
|
||||
def MotorsOff(self):
|
||||
"""
|
||||
|
@ -641,8 +643,8 @@ Sets all motors to stopped, useful when ending a program
|
|||
self.RawWrite(COMMAND_ALL_OFF, [0])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending motors off command!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending motors off command! {}'.format(str(e)))
|
||||
|
||||
def SetLed(self, state):
|
||||
"""
|
||||
|
@ -674,8 +676,8 @@ Reads the current state of the LED, False for off, True for on
|
|||
i2cRecv = self.RawRead(COMMAND_GET_LED, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading LED state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading LED state! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
if i2cRecv[1] == COMMAND_VALUE_OFF:
|
||||
|
@ -694,8 +696,8 @@ Resets the EPO latch state, use to allow movement again after the EPO has been t
|
|||
self.RawWrite(COMMAND_RESET_EPO, [0])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed resetting EPO!')
|
||||
except Exception as e:
|
||||
self.Print('Failed resetting EPO! {}'.format(str(e)))
|
||||
|
||||
def GetEpo(self):
|
||||
"""
|
||||
|
@ -711,8 +713,8 @@ If True the EPO has been tripped, movement is disabled if the EPO is not ignored
|
|||
i2cRecv = self.RawRead(COMMAND_GET_EPO, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading EPO ignore state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading EPO ignore state! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
if i2cRecv[1] == COMMAND_VALUE_OFF:
|
||||
|
@ -736,8 +738,8 @@ Sets the system to ignore or use the EPO latch, set to False if you have an EPO
|
|||
self.RawWrite(COMMAND_SET_EPO_IGNORE, [level])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending EPO ignore state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending EPO ignore state! {}'.format(str(e)))
|
||||
|
||||
def GetEpoIgnore(self):
|
||||
"""
|
||||
|
@ -750,8 +752,8 @@ Reads the system EPO ignore state, False for using the EPO latch, True for ignor
|
|||
i2cRecv = self.RawRead(COMMAND_GET_EPO_IGNORE, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading EPO ignore state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading EPO ignore state! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
if i2cRecv[1] == COMMAND_VALUE_OFF:
|
||||
|
@ -793,8 +795,8 @@ Use HasNewIrMessage() to see if there has been a new IR message since the last c
|
|||
i2cRecv = self.RawRead(COMMAND_GET_LAST_IR, I2C_LONG_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading IR message!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading IR message! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
message = ''
|
||||
|
@ -818,8 +820,8 @@ Sets if IR messages control the state of the LED, False for no effect, True for
|
|||
self.RawWrite(COMMAND_SET_LED_IR, [level])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending LED state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending LED state! {}'.format(str(e)))
|
||||
|
||||
def GetLedIr(self):
|
||||
"""
|
||||
|
@ -832,8 +834,8 @@ Reads if IR messages control the state of the LED, False for no effect, True for
|
|||
i2cRecv = self.RawRead(COMMAND_GET_LED_IR, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading LED state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading LED state! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
if i2cRecv[1] == COMMAND_VALUE_OFF:
|
||||
|
@ -853,8 +855,8 @@ Returns the value as a voltage based on the 3.3 V reference pin (pin 1).
|
|||
i2cRecv = self.RawRead(COMMAND_GET_ANALOG_1, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading analog level #1!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading analog level #1! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
raw = (i2cRecv[1] << 8) + i2cRecv[2]
|
||||
|
@ -873,8 +875,8 @@ Returns the value as a voltage based on the 3.3 V reference pin (pin 1).
|
|||
i2cRecv = self.RawRead(COMMAND_GET_ANALOG_2, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading analog level #2!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading analog level #2! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
raw = (i2cRecv[1] << 8) + i2cRecv[2]
|
||||
|
@ -900,8 +902,8 @@ The failsafe is disabled at power on
|
|||
self.RawWrite(COMMAND_SET_FAILSAFE, [level])
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed sending communications failsafe state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed sending communications failsafe state! {}'.format(str(e)))
|
||||
|
||||
def GetCommsFailsafe(self):
|
||||
"""
|
||||
|
@ -915,8 +917,8 @@ The failsafe will turn the motors off unless it is commanded at least once every
|
|||
i2cRecv = self.RawRead(COMMAND_GET_FAILSAFE, I2C_NORM_LEN)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.Print('Failed reading communications failsafe state!')
|
||||
except Exception as e:
|
||||
self.Print('Failed reading communications failsafe state! {}'.format(str(e)))
|
||||
return
|
||||
|
||||
if i2cRecv[1] == COMMAND_VALUE_OFF:
|
||||
|
@ -930,6 +932,7 @@ Help()
|
|||
|
||||
Displays the names and descriptions of the various functions and settings provided
|
||||
"""
|
||||
# noinspection PyTypeChecker
|
||||
funcList = [ZeroBorg.__dict__.get(a) for a in dir(ZeroBorg) if
|
||||
isinstance(ZeroBorg.__dict__.get(a), types.FunctionType)]
|
||||
funcListSorted = sorted(funcList, key=lambda x: x.func_code.co_firstlineno)
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import datetime
|
||||
import dateutil.parser
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.http.request import HttpRequestPlugin
|
||||
|
||||
class HttpRequestOtaBookingPlugin(HttpRequestPlugin):
|
||||
""" Plugin to send requests to the Booking Hub API """
|
||||
|
||||
def __init__(self, hotel_id, token, timeout=5, **kwargs):
|
||||
self.hotel_id = hotel_id
|
||||
self.token = token
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
@action
|
||||
def get_reservations(self, day='today'):
|
||||
url = 'https://hub-api.booking.com/v1/hotels/{}/reservations' \
|
||||
.format(self.hotel_id)
|
||||
|
||||
today = datetime.date.today().isoformat()
|
||||
if day == 'today': day = today
|
||||
|
||||
headers = { 'X-Booking-Auth-Token': self.token }
|
||||
params = { 'checkin': day }
|
||||
|
||||
response = self.get(url, headers=headers, params=params,
|
||||
output='json', timeout=self.timeout)
|
||||
|
||||
reservations = [res for res in response.output
|
||||
if res['status'] != 'CANCELLED']
|
||||
|
||||
response.output = {
|
||||
'reservations': reservations,
|
||||
'n_reservations': len(reservations),
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
@ -24,14 +24,12 @@ class HttpWebpagePlugin(Plugin):
|
|||
|
||||
_mercury_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mercury-parser.js')
|
||||
|
||||
def _parse(self, proc):
|
||||
output = ''
|
||||
|
||||
@staticmethod
|
||||
def _parse(proc):
|
||||
with subprocess.Popen(proc, stdout=subprocess.PIPE, stderr=None) as parser:
|
||||
output = parser.communicate()[0].decode()
|
||||
|
||||
return output
|
||||
return parser.communicate()[0].decode()
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
@action
|
||||
def simplify(self, url, type='html', html=None, outfile=None):
|
||||
"""
|
||||
|
@ -124,15 +122,12 @@ class HttpWebpagePlugin(Plugin):
|
|||
|
||||
weasyprint.HTML(string=content).write_pdf(outfile, stylesheets=css)
|
||||
else:
|
||||
content = '''
|
||||
<html>
|
||||
content = '''<html>
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<style>{style}</style>
|
||||
</head>
|
||||
<body>{{content}}</body>
|
||||
</html>
|
||||
'''.format(title=title, style=style, content=content)
|
||||
</head>'''.format(title=title, style=style, content=content) + \
|
||||
'<body>{{' + content + '}}</body></html>'
|
||||
|
||||
with open(outfile, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
|
|
@ -188,10 +188,10 @@ class InspectPlugin(Plugin):
|
|||
for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
|
||||
prefix=prefix,
|
||||
onerror=lambda x: None):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
module = importlib.import_module(modname)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not import module {modname}: {str(e)}')
|
||||
continue
|
||||
|
||||
for _, obj in inspect.getmembers(module):
|
||||
|
@ -207,10 +207,10 @@ class InspectPlugin(Plugin):
|
|||
for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
|
||||
prefix=prefix,
|
||||
onerror=lambda x: None):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
module = importlib.import_module(modname)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not import module {modname}: {str(e)}')
|
||||
continue
|
||||
|
||||
for _, obj in inspect.getmembers(module):
|
||||
|
@ -226,10 +226,10 @@ class InspectPlugin(Plugin):
|
|||
for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
|
||||
prefix=prefix,
|
||||
onerror=lambda x: None):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
module = importlib.import_module(modname)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not import module {modname}: {str(e)}')
|
||||
continue
|
||||
|
||||
for _, obj in inspect.getmembers(module):
|
||||
|
@ -250,10 +250,10 @@ class InspectPlugin(Plugin):
|
|||
for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
|
||||
prefix=prefix,
|
||||
onerror=lambda x: None):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
module = importlib.import_module(modname)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not import module {modname}: {str(e)}')
|
||||
continue
|
||||
|
||||
for _, obj in inspect.getmembers(module):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
from platypush.context import get_backend
|
||||
from platypush.plugins import Plugin, action
|
||||
|
@ -19,10 +18,14 @@ class KafkaPlugin(Plugin):
|
|||
* **kafka** (``pip install kafka-python``)
|
||||
"""
|
||||
|
||||
def __init__(self, server=None, **kwargs):
|
||||
def __init__(self, server=None, port=9092, **kwargs):
|
||||
"""
|
||||
:param server: Default Kafka server name or address + port (format: ``host:port``) to dispatch the messages to. If None (default), then it has to be specified upon message sending.
|
||||
:param server: Default Kafka server name or address. If None (default), then it has to be specified upon
|
||||
message sending.
|
||||
:type server: str
|
||||
|
||||
:param port: Default Kafka server port (default: 9092).
|
||||
:type port: int
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
@ -35,13 +38,17 @@ class KafkaPlugin(Plugin):
|
|||
# Kafka can be veryyyy noisy
|
||||
logging.getLogger('kafka').setLevel(logging.ERROR)
|
||||
|
||||
|
||||
@action
|
||||
def send_message(self, msg, topic, server=None, **kwargs):
|
||||
def send_message(self, msg, topic, server=None):
|
||||
"""
|
||||
:param msg: Message to send - as a string, bytes stream, JSON, Platypush message, dictionary, or anything that implements ``__str__``
|
||||
:param msg: Message to send - as a string, bytes stream, JSON, Platypush message, dictionary, or anything
|
||||
that implements ``__str__``
|
||||
|
||||
:param server: Kafka server name or address + port (format: ``host:port``). If None, then the default server will be used
|
||||
:param topic: Topic to send the message to.
|
||||
:type topic: str
|
||||
|
||||
:param server: Kafka server name or address + port (format: ``host:port``). If None, then the default server
|
||||
will be used
|
||||
:type server: str
|
||||
"""
|
||||
|
||||
|
@ -52,8 +59,8 @@ class KafkaPlugin(Plugin):
|
|||
try:
|
||||
kafka_backend = get_backend('kafka')
|
||||
server = kafka_backend.server
|
||||
except:
|
||||
raise RuntimeError('No Kafka server nor default server specified')
|
||||
except Exception as e:
|
||||
raise RuntimeError(f'No Kafka server nor default server specified: {str(e)}')
|
||||
else:
|
||||
server = self.server
|
||||
|
||||
|
@ -67,4 +74,3 @@ class KafkaPlugin(Plugin):
|
|||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import functools
|
|||
import os
|
||||
import queue
|
||||
import re
|
||||
import requests
|
||||
from typing import Optional, List, Dict, Union
|
||||
|
||||
import requests
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
|
@ -214,8 +214,8 @@ class MediaPlugin(Plugin):
|
|||
try:
|
||||
torrents = get_plugin(self.torrent_plugin)
|
||||
torrents.quit()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Could not stop torrent plugin: {str(e)}')
|
||||
|
||||
@action
|
||||
def play(self, resource, *args, **kwargs):
|
||||
|
@ -423,7 +423,6 @@ class MediaPlugin(Plugin):
|
|||
}
|
||||
|
||||
"""
|
||||
import requests
|
||||
|
||||
http = get_backend('http')
|
||||
if not http:
|
||||
|
@ -445,8 +444,6 @@ class MediaPlugin(Plugin):
|
|||
|
||||
@action
|
||||
def stop_streaming(self, media_id):
|
||||
import requests
|
||||
|
||||
http = get_backend('http')
|
||||
if not http:
|
||||
self.logger.warning('Cannot unregister {}: HTTP backend unavailable'.
|
||||
|
@ -536,7 +533,7 @@ class MediaPlugin(Plugin):
|
|||
return functools.reduce(
|
||||
lambda t, t_i: t + t_i,
|
||||
[float(t) * pow(60, i) for (i, t) in enumerate(re.search(
|
||||
'^Duration:\s*([^,]+)', [x.decode()
|
||||
r'^Duration:\s*([^,]+)', [x.decode()
|
||||
for x in result.stdout.readlines()
|
||||
if "Duration" in x.decode()].pop().strip()
|
||||
).group(1).split(':')[::-1])]
|
||||
|
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
import re
|
||||
import time
|
||||
|
||||
from platypush.context import get_plugin, get_bus
|
||||
from platypush.context import get_bus
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.media import MediaPlugin
|
||||
from platypush.utils import get_mime_type
|
||||
|
@ -91,6 +91,7 @@ class MediaChromecastPlugin(MediaPlugin):
|
|||
post_event(MediaStopEvent, device=self.name)
|
||||
if status.get('volume') != self.status.get('volume'):
|
||||
post_event(MediaVolumeChangedEvent, volume=status.get('volume'), device=self.name)
|
||||
# noinspection PyUnresolvedReferences
|
||||
if abs(status.get('position') - self.status.get('position')) > time.time() - self.last_status_timestamp + 5:
|
||||
post_event(MediaSeekEvent, position=status.get('position'), device=self.name)
|
||||
|
||||
|
@ -104,7 +105,7 @@ class MediaChromecastPlugin(MediaPlugin):
|
|||
self.initialized = False
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def new_media_status(self, status):
|
||||
def new_media_status(self, *_):
|
||||
if self.subtitle_id and not self.initialized:
|
||||
self.mc.update_status()
|
||||
self.mc.enable_subtitle(self.subtitle_id)
|
||||
|
@ -327,9 +328,9 @@ class MediaChromecastPlugin(MediaPlugin):
|
|||
|
||||
@classmethod
|
||||
def _get_youtube_url(cls, url):
|
||||
m = re.match('https?://www.youtube.com/watch\?v=([^&]+).*', url)
|
||||
m = re.match(r'https?://(www\.)?youtube\.com/watch\?v=([^&]+).*', url)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return m.group(2)
|
||||
|
||||
m = re.match('youtube:video:(.*)', url)
|
||||
if m:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
@ -7,7 +6,7 @@ from platypush.context import get_bus
|
|||
from platypush.plugins import action
|
||||
from platypush.plugins.media import MediaPlugin, PlayerState
|
||||
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, MediaStopEvent, \
|
||||
MediaSeekEvent, MediaVolumeChangedEvent, NewPlayingMediaEvent
|
||||
MediaSeekEvent, MediaVolumeChangedEvent
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
|
@ -602,7 +601,8 @@ class MediaKodiPlugin(MediaPlugin):
|
|||
try:
|
||||
kodi = self._get_kodi()
|
||||
players = kodi.Player.GetActivePlayers().get('result', [])
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not get active players: {str(e)}')
|
||||
return ret
|
||||
|
||||
ret['state'] = PlayerState.STOP.value
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import enum
|
||||
import math
|
||||
import urllib.parse
|
||||
|
||||
from platypush.context import get_bus
|
||||
|
@ -119,8 +118,8 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
|||
try:
|
||||
try:
|
||||
self._player.stop()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Could not stop player: {str(e)}')
|
||||
|
||||
self._player.quit()
|
||||
except OMXPlayerDeadError:
|
||||
|
|
|
@ -3,9 +3,7 @@ import os
|
|||
import re
|
||||
import time
|
||||
|
||||
from sqlalchemy import create_engine, func, or_, Column, Integer, String, \
|
||||
DateTime, PrimaryKeyConstraint, ForeignKey
|
||||
|
||||
from sqlalchemy import create_engine, Column, Integer, String, DateTime, PrimaryKeyConstraint, ForeignKey
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
@ -17,6 +15,7 @@ from platypush.plugins.media.search import MediaSearcher
|
|||
Base = declarative_base()
|
||||
Session = scoped_session(sessionmaker())
|
||||
|
||||
|
||||
class LocalMediaSearcher(MediaSearcher):
|
||||
"""
|
||||
This class will search for media in the local configured directories. It
|
||||
|
@ -29,7 +28,7 @@ class LocalMediaSearcher(MediaSearcher):
|
|||
* **sqlalchemy** (``pip install sqlalchemy``)
|
||||
"""
|
||||
|
||||
_filename_separators = '[.,_\-@()\[\]\{\}\s\'\"]+'
|
||||
_filename_separators = r'[.,_\-@()\[\]\{\}\s\'\"]+'
|
||||
|
||||
def __init__(self, dirs, *args, **kwargs):
|
||||
super().__init__()
|
||||
|
|
|
@ -97,8 +97,8 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
if Config.get(plugin_name):
|
||||
self._media_plugin = get_plugin(plugin_name)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Could not get media plugin {plugin_name}: {str(e)}')
|
||||
|
||||
if not self._media_plugin:
|
||||
raise RuntimeError(('No media player specified and no ' +
|
||||
|
@ -109,7 +109,7 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
def _read_process_line(self):
|
||||
line = self._webtorrent_process.stdout.readline().decode().strip()
|
||||
# Strip output of the colors
|
||||
return re.sub('\x1b\[(([0-9]+m)|(.{1,2}))', '', line).strip()
|
||||
return re.sub(r'\x1b\[(([0-9]+m)|(.{1,2}))', '', line).strip()
|
||||
|
||||
def _process_monitor(self, resource, download_dir, download_only,
|
||||
player_type, player_args):
|
||||
|
@ -142,7 +142,7 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
and state == TorrentState.IDLE:
|
||||
# IDLE -> DOWNLOADING_METADATA
|
||||
state = TorrentState.DOWNLOADING_METADATA
|
||||
bus.post(TorrentDownloadedMetadataEvent(resource=resource))
|
||||
bus.post(TorrentDownloadedMetadataEvent(url=webtorrent_url, resource=resource))
|
||||
elif 'downloading: ' in line.lower() \
|
||||
and media_file is None:
|
||||
# Find video files in torrent directory
|
||||
|
@ -177,10 +177,9 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
if state.value <= TorrentState.DOWNLOADING_METADATA.value \
|
||||
and media_file and webtorrent_url:
|
||||
# DOWNLOADING_METADATA -> DOWNLOADING
|
||||
state = TorrentState.DOWNLOADING
|
||||
bus.post(TorrentDownloadStartEvent(
|
||||
resource=resource, media_file=media_file,
|
||||
stream_url=webtorrent_url))
|
||||
stream_url=webtorrent_url, url=webtorrent_url))
|
||||
break
|
||||
|
||||
if not output_dir:
|
||||
|
@ -193,8 +192,11 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
self.logger.warning('WebTorrent could not start streaming')
|
||||
|
||||
# Keep downloading but don't start the player
|
||||
try: self._webtorrent_process.wait()
|
||||
except: pass
|
||||
try:
|
||||
self._webtorrent_process.wait()
|
||||
except Exception as e:
|
||||
self.logger.warning(f'WebTorrent process error: {str(e)}')
|
||||
|
||||
return
|
||||
|
||||
player = None
|
||||
|
@ -237,10 +239,14 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
self.logger.info('Torrent player terminated')
|
||||
bus.post(TorrentDownloadCompletedEvent(resource=resource,
|
||||
output_dir=output_dir,
|
||||
media_file=media_file))
|
||||
media_file=media_file,
|
||||
url=webtorrent_url))
|
||||
|
||||
try:
|
||||
self.quit()
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Could not terminate WebTorrent process: {str(e)}')
|
||||
|
||||
try: self.quit()
|
||||
except: pass
|
||||
self.logger.info('WebTorrent process terminated')
|
||||
|
||||
return _thread
|
||||
|
@ -252,6 +258,7 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
media_cls = player.__class__.__name__
|
||||
|
||||
if media_cls == 'MediaMplayerPlugin':
|
||||
# noinspection PyProtectedMember
|
||||
stop_evt = player._mplayer_stopped_event
|
||||
elif media_cls == 'MediaMpvPlugin' or media_cls == 'MediaVlcPlugin':
|
||||
stop_evt = threading.Event()
|
||||
|
@ -359,8 +366,8 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
stream_url = self._torrent_stream_urls.get(resource)
|
||||
|
||||
if not stream_url:
|
||||
return (None, ('The webtorrent process hasn\'t started ' +
|
||||
'streaming after {} seconds').format(
|
||||
return (None, ("The webtorrent process hasn't started " +
|
||||
"streaming after {} seconds").format(
|
||||
self._web_stream_ready_timeout))
|
||||
|
||||
return {'resource': resource, 'url': stream_url}
|
||||
|
@ -380,8 +387,10 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
|||
if self._is_process_alive():
|
||||
self._webtorrent_process.terminate()
|
||||
self._webtorrent_process.wait()
|
||||
try: self._webtorrent_process.kill()
|
||||
except: pass
|
||||
try:
|
||||
self._webtorrent_process.kill()
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Error on WebTorrent process kill: {str(e)}')
|
||||
|
||||
self._webtorrent_process = None
|
||||
|
||||
|
|
|
@ -173,11 +173,10 @@ class MqttPlugin(Plugin):
|
|||
if isinstance(msg, (dict, list)):
|
||||
msg = json.dumps(msg)
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
msg = Message.build(json.loads(msg))
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(f'Not a valid JSON: {str(e)}')
|
||||
|
||||
host = host or self.host
|
||||
port = port or self.port or 1883
|
||||
|
@ -209,11 +208,10 @@ class MqttPlugin(Plugin):
|
|||
response_buffer.close()
|
||||
|
||||
if client:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
client.loop_stop()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Could not stop client loop: {e}')
|
||||
|
||||
client.disconnect()
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
time.sleep(0.5)
|
||||
|
||||
self.client = None
|
||||
if error:
|
||||
raise error
|
||||
|
||||
def _exec(self, method, *args, **kwargs):
|
||||
|
@ -383,14 +384,14 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
if not resource:
|
||||
return
|
||||
|
||||
m = re.search('^https://open.spotify.com/([^?]+)', resource)
|
||||
m = re.search(r'^https?://open\.spotify\.com/([^?]+)', resource)
|
||||
if m:
|
||||
resource = 'spotify:{}'.format(m.group(1).replace('/', ':'))
|
||||
|
||||
if resource.startswith('spotify:'):
|
||||
resource = resource.split('?')[0]
|
||||
|
||||
m = re.match('spotify:playlist:(.*)', resource)
|
||||
m = re.match(r'spotify:playlist:(.*)', resource)
|
||||
if m:
|
||||
# Old Spotify URI format, convert it to new
|
||||
resource = 'spotify:user:spotify:playlist:' + m.group(1)
|
||||
|
@ -423,8 +424,8 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
"""
|
||||
Seek to the specified position (DEPRECATED, use :meth:`.seek` instead).
|
||||
|
||||
:param value: Seek position in seconds, or delta string (e.g. '+15' or '-15') to indicate a seek relative to the current position
|
||||
:type value: int
|
||||
:param value: Seek position in seconds, or delta string (e.g. '+15' or '-15') to indicate a seek relative to
|
||||
the current position :type value: int
|
||||
"""
|
||||
|
||||
return self.seek(value)
|
||||
|
@ -434,8 +435,8 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
"""
|
||||
Seek to the specified position
|
||||
|
||||
:param position: Seek position in seconds, or delta string (e.g. '+15' or '-15') to indicate a seek relative to the current position
|
||||
:type position: int
|
||||
:param position: Seek position in seconds, or delta string (e.g. '+15' or '-15') to indicate a seek relative
|
||||
to the current position :type position: int
|
||||
"""
|
||||
|
||||
return self._exec('seekcur', position)
|
||||
|
@ -486,6 +487,7 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
try:
|
||||
n_tries -= 1
|
||||
self._connect()
|
||||
if self.client:
|
||||
return self.client.status()
|
||||
except Exception as e:
|
||||
error = e
|
||||
|
@ -523,7 +525,7 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
or not track['artist']
|
||||
or re.search('^https?://', track['file'])
|
||||
or re.search('^tunein:', track['file'])):
|
||||
m = re.match('^\s*(.+?)\s+-\s+(.*)\s*$', track['title'])
|
||||
m = re.match(r'^\s*(.+?)\s+-\s+(.*)\s*$', track['title'])
|
||||
if m and m.group(1) and m.group(2):
|
||||
track['artist'] = m.group(1)
|
||||
track['title'] = m.group(2)
|
||||
|
@ -781,10 +783,7 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
items = self._exec('search', *filter, *args, return_status=False, **kwargs)
|
||||
|
||||
# Spotify results first
|
||||
items = sorted(items, key=lambda item:
|
||||
0 if item['file'].startswith('spotify:') else 1)
|
||||
|
||||
return items
|
||||
return sorted(items, key=lambda item: 0 if item['file'].startswith('spotify:') else 1)
|
||||
|
||||
# noinspection PyShadowingBuiltins
|
||||
@action
|
||||
|
|
|
@ -96,6 +96,7 @@ class MusicSnapcastPlugin(Plugin):
|
|||
'method': 'Server.GetStatus'
|
||||
}
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return (self._recv(sock) or {}).get('server', {})
|
||||
|
||||
|
@ -214,8 +215,8 @@ class MusicSnapcastPlugin(Plugin):
|
|||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'Error on socket close: {e}')
|
||||
|
||||
@action
|
||||
def mute(self, client=None, group=None, mute=None, host=None, port=None):
|
||||
|
@ -266,13 +267,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
request['params']['volume']['percent'] = client['config']['volume']['percent']
|
||||
request['params']['volume']['muted'] = not cur_muted if mute is None else mute
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def volume(self, client, volume=None, delta=None, mute=None, host=None,
|
||||
|
@ -336,13 +338,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
request['params']['volume'] = {}
|
||||
request['params']['volume']['percent'] = volume
|
||||
request['params']['volume']['muted'] = mute
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def set_client_name(self, client, name, host=None, port=None):
|
||||
|
@ -376,13 +379,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
client = self._get_client(sock, client)
|
||||
request['params']['id'] = client['id']
|
||||
request['params']['name'] = name
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def set_group_name(self, group, name, host=None, port=None):
|
||||
|
@ -416,13 +420,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
}
|
||||
}
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def set_latency(self, client, latency, host=None, port=None):
|
||||
|
@ -457,13 +462,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
|
||||
client = self._get_client(sock, client)
|
||||
request['params']['id'] = client['id']
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def delete_client(self, client, host=None, port=None):
|
||||
|
@ -493,13 +499,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
|
||||
client = self._get_client(sock, client)
|
||||
request['params']['id'] = client['id']
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def group_set_clients(self, group, clients, host=None, port=None):
|
||||
|
@ -538,13 +545,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
client = self._get_client(sock, client)
|
||||
request['params']['clients'].append(client['id'])
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def group_set_stream(self, group, stream_id, host=None, port=None):
|
||||
|
@ -579,13 +587,14 @@ class MusicSnapcastPlugin(Plugin):
|
|||
}
|
||||
}
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
self._send(sock, request)
|
||||
return self._recv(sock)
|
||||
finally:
|
||||
try:
|
||||
sock.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on socket close', e)
|
||||
|
||||
@action
|
||||
def get_backend_hosts(self):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -101,6 +100,7 @@ class PushbulletPlugin(Plugin):
|
|||
kwargs['type'] = 'link' if url else 'note'
|
||||
|
||||
if device:
|
||||
# noinspection PyTypeChecker
|
||||
kwargs['device_iden'] = device['iden']
|
||||
|
||||
resp = requests.post('https://api.pushbullet.com/v2/pushes',
|
||||
|
@ -143,6 +143,7 @@ class PushbulletPlugin(Plugin):
|
|||
raise Exception('Pushbullet file upload failed with status {}'.
|
||||
format(resp.status_code))
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
resp = requests.post('https://api.pushbullet.com/v2/pushes',
|
||||
headers={'Authorization': 'Bearer ' + self.token,
|
||||
'Content-Type': 'application/json'},
|
||||
|
|
|
@ -19,13 +19,12 @@ class RedisPlugin(Plugin):
|
|||
self.kwargs = kwargs
|
||||
|
||||
if not kwargs:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
redis_backend = get_backend('redis')
|
||||
if redis_backend and redis_backend.redis_args:
|
||||
self.kwargs = redis_backend.redis_args
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
|
||||
def _get_redis(self):
|
||||
return Redis(*self.args, **self.kwargs)
|
||||
|
|
|
@ -8,7 +8,6 @@ from platypush.plugins import action
|
|||
from platypush.plugins.sensor import SensorPlugin
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
class SerialPlugin(SensorPlugin):
|
||||
"""
|
||||
The serial plugin can read data from a serial device, as long as the serial
|
||||
|
@ -129,7 +128,8 @@ class SerialPlugin(SensorPlugin):
|
|||
if serial_available:
|
||||
try:
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
time.sleep(1)
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True)
|
||||
|
||||
|
@ -137,16 +137,15 @@ class SerialPlugin(SensorPlugin):
|
|||
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except:
|
||||
self.logger.warning('Invalid JSON message from {}: {}'.
|
||||
format(self.device, data))
|
||||
except (ValueError, TypeError):
|
||||
self.logger.warning('Invalid JSON message from {}: {}'.format(self.device, data))
|
||||
else:
|
||||
data = self.last_measurement
|
||||
finally:
|
||||
try:
|
||||
self.serial_lock.release()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
|
||||
if data:
|
||||
self.last_measurement = data
|
||||
|
@ -194,7 +193,8 @@ class SerialPlugin(SensorPlugin):
|
|||
if serial_available:
|
||||
try:
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
time.sleep(1)
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True)
|
||||
|
||||
|
@ -216,12 +216,12 @@ class SerialPlugin(SensorPlugin):
|
|||
finally:
|
||||
try:
|
||||
self.serial_lock.release()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
|
||||
try:
|
||||
data = data.decode()
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
data = base64.encodebytes(data)
|
||||
|
||||
return data
|
||||
|
@ -261,7 +261,8 @@ class SerialPlugin(SensorPlugin):
|
|||
if serial_available:
|
||||
try:
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate)
|
||||
except:
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
time.sleep(1)
|
||||
ser = self._get_serial(device=device, baud_rate=baud_rate, reset=True)
|
||||
|
||||
|
@ -270,8 +271,8 @@ class SerialPlugin(SensorPlugin):
|
|||
finally:
|
||||
try:
|
||||
self.serial_lock.release()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -166,8 +166,7 @@ class SoundPlugin(Plugin):
|
|||
|
||||
is_raw_stream = streamtype == sd.RawOutputStream
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def audio_callback(outdata, frames, frame_time, status):
|
||||
def audio_callback(outdata, frames, *, status):
|
||||
if self._get_playback_state(stream_index) == PlaybackState.STOPPED:
|
||||
raise sd.CallbackStop
|
||||
|
||||
|
@ -181,7 +180,7 @@ class SoundPlugin(Plugin):
|
|||
|
||||
if status.output_underflow:
|
||||
self.logger.warning('Output underflow: increase blocksize?')
|
||||
outdata = (b'\x00' if is_raw_stream else 0.) * len(outdata)
|
||||
outdata[:] = (b'\x00' if is_raw_stream else 0.) * len(outdata)
|
||||
return
|
||||
|
||||
if status:
|
||||
|
@ -397,7 +396,6 @@ class SoundPlugin(Plugin):
|
|||
finally:
|
||||
if f and not f.closed:
|
||||
f.close()
|
||||
f = None
|
||||
|
||||
self.stop_playback([stream_index])
|
||||
|
||||
|
|
|
@ -221,10 +221,9 @@ class SshPlugin(Plugin):
|
|||
client = self._connect(**kwargs)
|
||||
|
||||
def decode(buf: bytes) -> str:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
buf = buf.decode()
|
||||
except:
|
||||
except (ValueError, TypeError):
|
||||
buf = base64.encodebytes(buf).decode()
|
||||
|
||||
if buf.endswith('\n'):
|
||||
|
@ -341,11 +340,10 @@ class SshPlugin(Plugin):
|
|||
|
||||
try:
|
||||
if os.path.isdir(local_path):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
sftp.mkdir(remote_path)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'mkdir {remote_path}: {e}')
|
||||
|
||||
assert recursive, '{} is a directory but recursive has been set to False'.format(local_path)
|
||||
assert self.is_directory(sftp, remote_path), \
|
||||
|
@ -355,11 +353,10 @@ class SshPlugin(Plugin):
|
|||
os.chdir(local_path)
|
||||
|
||||
for path, folders, files in os.walk('.'):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
sftp.mkdir(path)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning(f'mkdir {remote_path}: {e}')
|
||||
|
||||
for file in files:
|
||||
src = os.path.join(path, file)
|
||||
|
|
|
@ -47,12 +47,11 @@ def reverse_tunnel(server_port, remote_host, remote_port, transport, bind_addr='
|
|||
should_run[key] = True
|
||||
|
||||
while should_run.get(key):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
chan = transport.accept(1)
|
||||
if chan is None:
|
||||
raise AssertionError
|
||||
except:
|
||||
assert chan is not None
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
continue
|
||||
|
||||
thr = threading.Thread(
|
||||
|
|
|
@ -128,7 +128,6 @@ class SwitchSwitchbotPlugin(SwitchPlugin, BluetoothBlePlugin):
|
|||
compatible_devices = {}
|
||||
|
||||
for dev in devices:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
characteristics = [
|
||||
chrc for chrc in self.discover_characteristics(
|
||||
|
@ -139,8 +138,8 @@ class SwitchSwitchbotPlugin(SwitchPlugin, BluetoothBlePlugin):
|
|||
|
||||
if characteristics:
|
||||
compatible_devices[dev['addr']] = None
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.warning('Device scan error', e)
|
||||
|
||||
return BluetoothScanResponse(devices=compatible_devices)
|
||||
|
||||
|
|
|
@ -83,11 +83,10 @@ class TensorflowPlugin(Plugin):
|
|||
assert success, 'Unable to acquire the model lock'
|
||||
yield
|
||||
finally:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self._model_locks[model_name].release()
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.logger.info(f'Model {model_name} lock release error: {e}')
|
||||
|
||||
def _load_model(self, model_name: str, reload: bool = False) -> Model:
|
||||
if model_name in self.models and not reload:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import requests
|
||||
from typing import Callable, Dict, Any, List, Optional
|
||||
from typing import Callable, Dict, Any, List
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import json
|
||||
import urllib3
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.plugins.media import PlayerState
|
||||
|
||||
class VideoTorrentcastPlugin(Plugin):
|
||||
def __init__(self, server='localhost', port=9090, *args, **kwargs):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.state = PlayerState.STOP.value
|
||||
|
||||
@action
|
||||
def play(self, url):
|
||||
request = urllib.request.urlopen(
|
||||
'http://{}:{}/play/'.format(self.server, self.port),
|
||||
data=urllib.parse.urlencode({
|
||||
'url': url
|
||||
}).encode()
|
||||
)
|
||||
|
||||
self.state = PlayerState.PLAY.value
|
||||
return request.read()
|
||||
|
||||
@action
|
||||
def pause(self):
|
||||
http = urllib3.PoolManager()
|
||||
request = http.request('POST',
|
||||
'http://{}:{}/pause/'.format(self.server, self.port))
|
||||
|
||||
self.state = PlayerState.PAUSE.value
|
||||
return request.read()
|
||||
|
||||
@action
|
||||
def stop(self):
|
||||
http = urllib3.PoolManager()
|
||||
request = http.request('POST',
|
||||
'http://{}:{}/stop/'.format(self.server, self.port))
|
||||
|
||||
self.state = PlayerState.STOP.value
|
||||
return request.read()
|
||||
|
||||
@action
|
||||
def search(self, query):
|
||||
request = urllib.request.urlopen(urllib.request.Request(
|
||||
'https://api.apidomain.info/list?' + urllib.parse.urlencode({
|
||||
'sort': 'relevance',
|
||||
'quality': '720p,1080p,3d',
|
||||
'page': 1,
|
||||
'keywords': query,
|
||||
}),
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' +
|
||||
'(KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
|
||||
})
|
||||
)
|
||||
|
||||
results = json.loads(request.read())
|
||||
return results
|
||||
|
||||
@action
|
||||
def search_and_play(self, query):
|
||||
response = self.search(query)
|
||||
if not response.output['MovieList']:
|
||||
self.logger.info('No torrent results found for {}'.format(query))
|
||||
|
||||
item = response.output['MovieList'][0]
|
||||
magnet = item['items'][0]['torrent_magnet']
|
||||
self.logger.info('Playing torrent "{}" from {}'
|
||||
.format(item['title'], magnet))
|
||||
|
||||
return self.play(magnet)
|
||||
|
||||
@action
|
||||
def voldown(self): raise NotImplementedError()
|
||||
|
||||
@action
|
||||
def volup(self): raise NotImplementedError()
|
||||
|
||||
@action
|
||||
def back(self): raise NotImplementedError()
|
||||
|
||||
@action
|
||||
def forward(self): raise NotImplementedError()
|
||||
|
||||
@action
|
||||
def status(self): return { 'state': self.state }
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
@ -3,8 +3,7 @@ import socket
|
|||
import time
|
||||
from typing import List, Dict, Any, Optional, Union
|
||||
|
||||
import zeroconf
|
||||
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser
|
||||
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser, ServiceListener, ZeroconfServiceTypes
|
||||
|
||||
from platypush.context import get_bus
|
||||
from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, ZeroconfServiceRemovedEvent, \
|
||||
|
@ -12,7 +11,7 @@ from platypush.message.event.zeroconf import ZeroconfServiceAddedEvent, Zeroconf
|
|||
from platypush.plugins import Plugin, action
|
||||
|
||||
|
||||
class ZeroconfListener(zeroconf.ServiceListener):
|
||||
class ZeroconfListener(ServiceListener):
|
||||
def __init__(self, evt_queue: queue.Queue):
|
||||
super().__init__()
|
||||
self.evt_queue = evt_queue
|
||||
|
@ -79,7 +78,7 @@ class ZeroconfPlugin(Plugin):
|
|||
:param timeout: Discovery timeout in seconds (default: 5).
|
||||
:return: List of the services as strings.
|
||||
"""
|
||||
return list(zeroconf.ZeroconfServiceTypes.find(timeout=timeout))
|
||||
return list(ZeroconfServiceTypes.find(timeout=timeout))
|
||||
|
||||
@action
|
||||
def discover_service(self, service: Union[str, list], timeout: Optional[int] = 5) -> Dict[str, Any]:
|
||||
|
|
|
@ -125,7 +125,8 @@ class ZigbeeMqttPlugin(MqttPlugin, SwitchPlugin):
|
|||
:param password: If the connection requires user authentication, specify the password (default: None)
|
||||
"""
|
||||
|
||||
super().__init__(host=host, port=port, tls_certfile=tls_certfile, tls_keyfile=tls_keyfile,
|
||||
SwitchPlugin.__init__(self)
|
||||
MqttPlugin.__init__(self, host=host, port=port, tls_certfile=tls_certfile, tls_keyfile=tls_keyfile,
|
||||
tls_version=tls_version, tls_ciphers=tls_ciphers, username=username,
|
||||
password=password, **kwargs)
|
||||
|
||||
|
|
|
@ -270,12 +270,12 @@ class ForProcedure(LoopProcedure):
|
|||
self.iterable = iterable
|
||||
|
||||
def execute(self, _async=None, **context):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
iterable = eval(self.iterable)
|
||||
assert hasattr(iterable, '__iter__'), 'Object of type {} is not iterable: {}'.\
|
||||
format(type(iterable), iterable)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.debug(f'Iterable {self.iterable} expansion error: {e}')
|
||||
iterable = Request.expand_value_from_context(self.iterable, **context)
|
||||
|
||||
response = Response()
|
||||
|
@ -334,10 +334,10 @@ class WhileProcedure(LoopProcedure):
|
|||
@staticmethod
|
||||
def _get_context(**context):
|
||||
for (k, v) in context.items():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
context[k] = eval(v)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error for {v}: {e}')
|
||||
if isinstance(v, str):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
|
@ -356,8 +356,8 @@ class WhileProcedure(LoopProcedure):
|
|||
for k, v in context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
|
||||
while True:
|
||||
condition_true = eval(self.condition)
|
||||
|
@ -386,8 +386,8 @@ class WhileProcedure(LoopProcedure):
|
|||
for k, v in new_context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
|
||||
return response
|
||||
|
||||
|
@ -453,7 +453,8 @@ class IfProcedure(Procedure):
|
|||
for (k, v) in context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
||||
|
|
|
@ -169,14 +169,6 @@ class UserManager:
|
|||
"""
|
||||
session = self._get_db_session()
|
||||
return session.query(User).join(UserSession).filter_by(session_token=session_token).first()
|
||||
if not user:
|
||||
return None
|
||||
|
||||
return {
|
||||
'user_id': user.user_id,
|
||||
'username': user.username,
|
||||
'created_at': user.created_at,
|
||||
}
|
||||
|
||||
def generate_jwt_token(self, username: str, password: str, expires_at: Optional[datetime.datetime] = None) -> str:
|
||||
"""
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue