diff --git a/README.md b/README.md index b88b9c3..bf7bb45 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ You can find a sample project, using OpenAI/ChatGPT with this library here: http `pip install webex_bot` +If you need optional proxy support, use this command instead: + +`pip install webex_bot[proxy]` + 2. On the Webex Developer portal, create a new [bot token][3] and expose it as an environment variable. ```sh @@ -51,11 +55,19 @@ import os from webex_bot.commands.echo import EchoCommand from webex_bot.webex_bot import WebexBot +# (Optional) Proxy configuration +# Supports https or wss proxy, wss prioritized. +proxies = { + 'https': 'http://proxy.esl.example.com:80', + 'wss': 'socks5://proxy.esl.example.com:1080' +} + # Create a Bot Object bot = WebexBot(teams_bot_token=os.getenv("WEBEX_TEAMS_ACCESS_TOKEN"), approved_rooms=['06586d8d-6aad-4201-9a69-0bf9eeb5766e'], bot_name="My Teams Ops Bot", - include_demo_commands=True) + include_demo_commands=True, + proxies=proxies) # Add new commands for the bot to listen out for. bot.add_command(EchoCommand()) @@ -363,4 +375,4 @@ and off you go! [i13]: https://github.com/fbradyirl/webex_bot/issues/13 -[i20]: https://github.com/fbradyirl/webex_bot/issues/20 +[i20]: https://github.com/fbradyirl/webex_bot/issues/20 \ No newline at end of file diff --git a/example.py b/example.py index a077263..075b00d 100644 --- a/example.py +++ b/example.py @@ -3,11 +3,19 @@ from webex_bot.commands.echo import EchoCommand from webex_bot.webex_bot import WebexBot +# (Optional) Proxy configuration +# Supports https or wss proxy, wss prioritized. +proxies = { + 'https': 'http://proxy.esl.example.com:80', + 'wss': 'socks5://proxy.esl.example.com:1080' +} + # Create a Bot Object bot = WebexBot(teams_bot_token=os.getenv("WEBEX_TEAMS_ACCESS_TOKEN"), approved_rooms=['06586d8d-6aad-4201-9a69-0bf9eeb5766e'], bot_name="My Teams Ops Bot", - include_demo_commands=True) + include_demo_commands=True, + proxies=proxies) # Add new commands for the bot to listen out for. bot.add_command(EchoCommand()) diff --git a/setup.py b/setup.py index 77f2aed..bb8e2cd 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,10 @@ test_requirements = ['pytest>=3', ] +extras_requirements = { + "proxy": ["websockets_proxy>=0.1.1"] +} + setup( author="Finbarr Brady", author_email='finbarr@somemail.com', @@ -26,6 +30,7 @@ 'Programming Language :: Python :: 3.9', ], description="Python package for a Webex Bot based on websockets.", + extras_require=extras_requirements, install_requires=requirements, license="MIT license", long_description=readme, diff --git a/webex_bot/webex_bot.py b/webex_bot/webex_bot.py index 12abcde..48859e3 100644 --- a/webex_bot/webex_bot.py +++ b/webex_bot/webex_bot.py @@ -31,7 +31,8 @@ def __init__(self, bot_help_subtitle="Here are my available commands. Click one to begin.", threads=True, help_command=None, - log_level="INFO"): + log_level="INFO", + proxies=None): """ Initialise WebexBot. @@ -46,7 +47,7 @@ def __init__(self, @param threads: If True, respond to msg by creating a thread. @param help_command: If None, use internal HelpCommand, otherwise override. @param log_level: Set loggin level. - + @param proxies: Dictionary of proxies for connections. """ coloredlogs.install(level=os.getenv("LOG_LEVEL", log_level), @@ -58,7 +59,8 @@ def __init__(self, teams_bot_token, on_message=self.process_incoming_message, on_card_action=self.process_incoming_card_action, - device_url=device_url) + device_url=device_url, + proxies=proxies) if help_command is None: self.help_command = HelpCommand( diff --git a/webex_bot/websockets/webex_websocket_client.py b/webex_bot/websockets/webex_websocket_client.py index be7b99c..f6ee65c 100644 --- a/webex_bot/websockets/webex_websocket_client.py +++ b/webex_bot/websockets/webex_websocket_client.py @@ -11,6 +11,12 @@ import websockets from webexteamssdk import WebexTeamsAPI +try: + from websockets_proxy import Proxy, proxy_connect +except ImportError: + Proxy = None + proxy_connect = None + logger = logging.getLogger(__name__) DEFAULT_DEVICE_URL = "https://wdm-a.wbx2.com/wdm/api/v1" @@ -36,16 +42,23 @@ def __init__(self, access_token, device_url=DEFAULT_DEVICE_URL, on_message=None, - on_card_action=None): + on_card_action=None, + proxies=None): self.access_token = access_token - self.teams = WebexTeamsAPI(access_token=access_token) + self.teams = WebexTeamsAPI(access_token=access_token, proxies=proxies) self.device_url = device_url self.device_info = None self.on_message = on_message self.on_card_action = on_card_action + self.proxies = proxies self.websocket = None self.share_id = None + if self.proxies: + # Connecting through a proxy + if proxy_connect is None: + raise ImportError("Failed to load libraries for proxy, maybe forgot [proxy] option during installation.") + def _process_incoming_websocket_message(self, msg): """ Handle websocket data. @@ -124,7 +137,8 @@ def _get_base64_message_id(self, activity): f"{verb}/{activity_id}") headers = {"Authorization": f"Bearer {self.access_token}"} conversation_message = requests.get(conversation_message_url, - headers=headers).json() + headers=headers, + proxies=self.proxies).json() logger.debug(f"conversation_message={conversation_message}") return conversation_message['id'] @@ -197,7 +211,20 @@ async def _websocket_recv(): async def _connect_and_listen(): ws_url = self.device_info['webSocketUrl'] logger.info(f"Opening websocket connection to {ws_url}") - async with websockets.connect(ws_url, ssl=ssl_context) as _websocket: + + if self.proxies and "wss" in self.proxies: + logger.info(f"Using proxy for websocket connection: {self.proxies['wss']}") + proxy = Proxy.from_url(self.proxies["wss"]) + connect = proxy_connect(ws_url, ssl=ssl_context, proxy=proxy) + elif self.proxies and "https" in self.proxies: + logger.info(f"Using proxy for websocket connection: {self.proxies['https']}") + proxy = Proxy.from_url(self.proxies["https"]) + connect = proxy_connect(ws_url, ssl=ssl_context, proxy=proxy) + else: + logger.debug(f"Not using proxy for websocket connection.") + connect = websockets.connect(ws_url, ssl=ssl_context) + + async with connect as _websocket: self.websocket = _websocket logger.info("WebSocket Opened.") msg = {'id': str(uuid.uuid4()), @@ -216,4 +243,4 @@ async def _connect_and_listen(): logger.error('could not create device info') raise Exception("No WDM device info") # trigger re-connect - asyncio.get_event_loop().run_until_complete(_connect_and_listen()) + asyncio.get_event_loop().run_until_complete(_connect_and_listen()) \ No newline at end of file