Skip to content

Instantly share code, notes, and snippets.

@scythargon
Last active August 1, 2020 11:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scythargon/a4f26342365a0ef82afbb28333cf69cc to your computer and use it in GitHub Desktop.
Save scythargon/a4f26342365a0ef82afbb28333cf69cc to your computer and use it in GitHub Desktop.
Websocket communication between slack and webpage
"""
As a client can use the next html page:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
window.addEventListener("load", function() {
// create websocket instance
var mySocket = new WebSocket("ws://localhost:8080/ws");
// add event listener reacting when message is received
mySocket.onmessage = function (event) {
var output = document.getElementById("output");
// put text into our output div
output.textContent = event.data;
};
var form = document.getElementsByClassName("foo");
var input = document.getElementById("input");
form[0].addEventListener("submit", function (e) {
// on forms submission send input to our server
input_text = input.value;
mySocket.send(input_text);
e.preventDefault()
})
});
</script>
<style>
/* just some super ugly css to make things bit more readable*/
div {
margin: 10em;
}
form {
margin: 10em;
}
</style>
</head>
<body>
<form class="foo">
<input id="input"></input>
<input type="submit"></input>
</form>
<div id="output"></div>
</body>
</html>
"""
import asyncio
import os
import threading
from slack import RTMClient, WebClient
from slack.errors import SlackApiError
import random
from autobahn.asyncio.websocket import (
WebSocketServerProtocol,
WebSocketServerFactory
)
slack_client = WebClient(
token=os.environ['SLACK_API_TOKEN']
)
@RTMClient.run_on(event='message')
def say_hello(**payload):
data = payload['data']
web_client = payload['web_client']
rtm_client = payload['rtm_client']
text = data.get('text', '')
# print(data)
if 'Hello' in text:
# print('Received message')
channel_id = data['channel']
thread_ts = data['ts']
user = data['user']
try:
rtm_client.factory.broadcast(f"New message from slack: {text}")
rtm_client.factory.last_channel = channel_id
rtm_client.factory.last_thread_ts = thread_ts
response = web_client.chat_postMessage(
channel=channel_id,
text=f"Hi <@{user}>!",
thread_ts=thread_ts
)
except SlackApiError as e:
# You will get a SlackApiError if "ok" is False
assert e.response["ok"] is False
assert e.response["error"] # str like 'invalid_auth', 'channel_not_found'
print(f"Got an error: {e.response['error']}")
def run_slack_client(factory):
second_loop = asyncio.new_event_loop()
asyncio.set_event_loop(second_loop)
rtm_client = RTMClient(token=os.environ["SLACK_API_TOKEN"])
rtm_client.factory = factory
rtm_client.start()
class SomeServerProtocol(WebSocketServerProtocol):
def onOpen(self):
"""
Connection from client is opened. Fires after opening
websockets handshake has been completed and we can send
and receive messages.
Register client in factory, so that it is able to track it.
Try to find conversation partner for this client.
"""
self.factory.register(self)
self.factory.findPartner(self)
def connectionLost(self, reason):
"""
Client lost connection, either disconnected or some error.
Remove client from list of tracked connections.
"""
self.factory.unregister(self)
def onMessage(self, payload, isBinary):
"""
Message sent from client, communicate this message to its conversation partner,
"""
self.factory.communicate(self, payload, isBinary)
class ChatRouletteFactory(WebSocketServerFactory):
def __init__(self, *args, **kwargs):
super(ChatRouletteFactory, self).__init__(*args, **kwargs)
self.clients = {}
def register(self, client):
"""
Add client to list of managed connections.
"""
self.clients[client.peer] = {"object": client, "partner": None}
def unregister(self, client):
"""
Remove client from list of managed connections.
"""
self.clients.pop(client.peer)
def findPartner(self, client):
"""
Find chat partner for a client. Check if there any of tracked clients
is idle. If there is no idle client just exit quietly. If there is
available partner assign him/her to our client.
"""
available_partners = [c for c in self.clients if c != client.peer and not self.clients[c]["partner"]]
if not available_partners:
print("no partners for {} check in a moment".format(client.peer))
else:
partner_key = random.choice(available_partners)
self.clients[partner_key]["partner"] = client
self.clients[client.peer]["partner"] = self.clients[partner_key]["object"]
def communicate(self, client, payload, isBinary):
"""
Broker message from client to its partner.
"""
c = self.clients[client.peer]
if self.last_channel and self.last_thread_ts:
slack_client.chat_postMessage(
channel=self.last_channel,
text=f"Message from chat: {payload.decode('utf-8')}",
thread_ts=self.last_thread_ts
)
if not c["partner"]:
c["object"].sendMessage("Sorry you dont have partner yet, check back in a minute".encode('utf-8'))
else:
c["partner"].sendMessage(payload)
def broadcast(self, message):
"""Send a slack message to all connected clients."""
print('broadcast call')
for client in self.clients.values():
client["object"].sendMessage(message.encode('utf-8'))
if __name__ == '__main__':
factory = ChatRouletteFactory("ws://127.0.0.1:9000")
factory.protocol = SomeServerProtocol
loop = asyncio.get_event_loop()
thread = threading.Thread(target=run_slack_client, args=(factory,))
thread.daemon = True
thread.start()
coro = loop.create_server(factory, '0.0.0.0', 9000)
server = loop.run_until_complete(coro)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server.close()
loop.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment