Skip to content

Commit 349c990

Browse files
committed
Add Rocket.Chat integration
1 parent 4af8bcd commit 349c990

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Zulip <--> Rocket.Chat mirror
2+
3+
## Usage
4+
5+
0. `pip install zulip rocketchat_API`
6+
7+
### 1. Zulip endpoint
8+
1. Create a generic Zulip bot
9+
2. (don't forget this step!) Make sure the bot is subscribed to the relevant stream
10+
3. Enter the bot's email and api_key into rocket_mirror_config.py
11+
4. Enter the destination subject and realm into the config file
12+
13+
### 2. Rocket.Chat endpoint
14+
1. Create a user
15+
2. Enter the user's username and password into rocket_mirror_config.py
16+
3. Enter the Rocket.Chat server url into the config file
17+
4. Enter the channel id and channel name to be mirrored into the config file
18+
19+
After the steps above have been completed, run `./rocket.chat-mirror` to start the mirroring.
20+
note: Run the script relative to its directory !
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import logging
5+
import time
6+
import signal
7+
import traceback
8+
import multiprocessing as mp
9+
import datetime
10+
11+
from types import FrameType
12+
from typing import Any, Callable, Dict
13+
14+
from rocketchat_API.rocketchat import RocketChat
15+
from rocket_mirror_config import config
16+
import zulip
17+
from pprint import pprint
18+
19+
Cfg = Dict[str, Any]
20+
21+
def die(signal: int, frame: FrameType) -> None:
22+
23+
# We actually want to exit, so run os._exit (so as not to be caught and restarted)
24+
os._exit(1)
25+
26+
def zulip_to_rocket_username(full_name: str, site: str) -> str:
27+
return "@**{0}**:{1}".format(full_name, site)
28+
29+
def rocket_to_zulip(zulip_client: zulip.Client, cfg: Cfg, res: Dict[str, Any]) -> None:
30+
zulip_cfg = cfg["zulip"]
31+
rocket_cfg = cfg["rocket"]
32+
33+
if res['success']:
34+
for msg in res['messages']:
35+
user = msg['u']
36+
content = "**{0}**: {1}".format(user['name'], msg['msg'])
37+
pprint(msg['u']['username'])
38+
39+
is_not_from_bot = user['username'] != rocket_cfg['username']
40+
if is_not_from_bot:
41+
msg_data = dict(
42+
sender=zulip_client.email,
43+
type="stream",
44+
to=zulip_cfg["stream"],
45+
subject=zulip_cfg["subject"],
46+
content=content)
47+
print(msg_data)
48+
zulip_client.send_message(msg_data)
49+
50+
def zulip_to_rocket(rocket_client: RocketChat,
51+
cfg: Cfg) -> Callable[[Dict[str, Any]], None]:
52+
zulip_cfg = cfg["zulip"]
53+
rocket_cfg = cfg["rocket"]
54+
site_without_http = zulip_cfg["site"].replace("https://", "").replace("http://", "")
55+
56+
def _zulip_to_rocket(msg: Dict[str, Any]) -> None:
57+
"""Zulip -> Matrix
58+
"""
59+
isa_stream = msg["type"] == "stream"
60+
not_from_bot = msg["sender_email"] != zulip_cfg["email"]
61+
in_the_specified_stream = msg["display_recipient"] == zulip_cfg["stream"]
62+
at_the_specified_subject = msg["subject"] == zulip_cfg["subject"]
63+
if isa_stream and not_from_bot and in_the_specified_stream and at_the_specified_subject:
64+
rocket_username = zulip_to_rocket_username(msg["sender_full_name"], site_without_http)
65+
rocket_text = "{0}: {1}".format(rocket_username,
66+
msg["content"])
67+
pprint(rocket_client.chat_post_message(rocket_text, channel=rocket_cfg["channel_id"]).json())
68+
return _zulip_to_rocket
69+
70+
def rocket_listener(rocket_client: RocketChat, zulip_client: zulip.Client, cfg: Cfg):
71+
interval = 2
72+
rocket_cfg = cfg["rocket"]
73+
while True:
74+
now = datetime.datetime.utcnow()
75+
oldest = now - datetime.timedelta(seconds=interval)
76+
oldest_str = str(oldest)[:-3] + 'Z'
77+
res = rocket_client.channels_history(rocket_cfg['channel_id'],
78+
oldest=oldest_str).json()
79+
rocket_to_zulip(zulip_client, cfg, res)
80+
time.sleep(interval)
81+
82+
if __name__ == '__main__':
83+
signal.signal(signal.SIGINT, die)
84+
logging.basicConfig(level=logging.WARNING)
85+
86+
# Get config for each clients
87+
zulip_config = config["zulip"]
88+
rocket_config = config["rocket"]
89+
90+
# Initiate clients
91+
print("Starting rocketchat mirroring bot")
92+
93+
backoff = zulip.RandomExponentialBackoff(timeout_success_equivalent=300)
94+
while backoff.keep_going():
95+
try:
96+
zulip_client = zulip.Client(email=zulip_config["email"],
97+
api_key=zulip_config["api_key"],
98+
site=zulip_config["site"])
99+
100+
rocket_client = RocketChat(rocket_config['username'],
101+
rocket_config['password'],
102+
server_url=rocket_config['server_url'])
103+
104+
# A bidirectional mirror
105+
106+
# returns configured function handler
107+
zulip_message_handler = zulip_to_rocket(rocket_client, config)
108+
p1 = mp.Process(target=zulip_client.call_on_each_message,
109+
args=(zulip_message_handler,))
110+
111+
p2 = mp.Process(target=rocket_listener,
112+
args=(rocket_client, zulip_client, config))
113+
114+
print("Starting message handler on Zulip client")
115+
p1.start()
116+
print("Starting message handler on Rocket.Chat client")
117+
p2.start()
118+
119+
p1.join()
120+
p2.join()
121+
122+
except Exception:
123+
traceback.print_exc()
124+
backoff.fail()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
config = {
2+
"rocket": {
3+
"server_url": "https://chat.serverurl.org",
4+
"username": "botusername",
5+
"password": "botpassword",
6+
"channel_id": "idofthechannel",
7+
"channel_name": "nameofthechannel"
8+
},
9+
"zulip": {
10+
"email": "[email protected]",
11+
"api_key": "someapikey",
12+
"site": "https://chat.someserver.org",
13+
"stream": "somestream",
14+
"subject": "somesubject"
15+
}
16+
}

0 commit comments

Comments
 (0)