-
-
Notifications
You must be signed in to change notification settings - Fork 393
Add Rocket.Chat integration #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rht
wants to merge
1
commit into
zulip:main
Choose a base branch
from
rht:rocket.chat
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Zulip <--> Rocket.Chat mirror | ||
|
||
## Usage | ||
|
||
0. `pip install zulip rocketchat_API` | ||
|
||
### 1. Zulip endpoint | ||
1. Create a generic Zulip bot | ||
2. (don't forget this step!) Make sure the bot is subscribed to the relevant stream | ||
3. Enter the bot's email and api_key into rocket_mirror_config.py | ||
4. Enter the destination subject and realm into the config file | ||
|
||
### 2. Rocket.Chat endpoint | ||
1. Create a user | ||
2. Enter the user's username and password into rocket_mirror_config.py | ||
3. Enter the Rocket.Chat server url into the config file | ||
4. Enter the channel id and channel name to be mirrored into the config file | ||
|
||
After the steps above have been completed, run `./rocket.chat-mirror` to start the mirroring. | ||
note: Run the script relative to its directory ! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import os | ||
import logging | ||
import time | ||
import signal | ||
import traceback | ||
import multiprocessing as mp | ||
import datetime | ||
|
||
from types import FrameType | ||
from typing import Any, Callable, Dict | ||
|
||
from rocketchat_API.rocketchat import RocketChat | ||
from rocket_mirror_config import config | ||
import zulip | ||
from pprint import pprint | ||
|
||
Cfg = Dict[str, Any] | ||
|
||
def die(signal: int, frame: FrameType) -> None: | ||
|
||
# We actually want to exit, so run os._exit (so as not to be caught and restarted) | ||
os._exit(1) | ||
|
||
def zulip_to_rocket_username(full_name: str, site: str) -> str: | ||
return "@**{0}**:{1}".format(full_name, site) | ||
|
||
def rocket_to_zulip(zulip_client: zulip.Client, cfg: Cfg, res: Dict[str, Any]) -> None: | ||
zulip_cfg = cfg["zulip"] | ||
rocket_cfg = cfg["rocket"] | ||
|
||
if res['success']: | ||
for msg in res['messages']: | ||
user = msg['u'] | ||
content = "**{0}**: {1}".format(user['name'], msg['msg']) | ||
pprint(msg['u']['username']) | ||
|
||
is_not_from_bot = user['username'] != rocket_cfg['username'] | ||
if is_not_from_bot: | ||
msg_data = dict( | ||
sender=zulip_client.email, | ||
type="stream", | ||
to=zulip_cfg["stream"], | ||
subject=zulip_cfg["subject"], | ||
content=content) | ||
print(msg_data) | ||
zulip_client.send_message(msg_data) | ||
|
||
def zulip_to_rocket(rocket_client: RocketChat, | ||
cfg: Cfg) -> Callable[[Dict[str, Any]], None]: | ||
zulip_cfg = cfg["zulip"] | ||
rocket_cfg = cfg["rocket"] | ||
site_without_http = zulip_cfg["site"].replace("https://", "").replace("http://", "") | ||
|
||
def _zulip_to_rocket(msg: Dict[str, Any]) -> None: | ||
"""Zulip -> Matrix | ||
""" | ||
isa_stream = msg["type"] == "stream" | ||
not_from_bot = msg["sender_email"] != zulip_cfg["email"] | ||
in_the_specified_stream = msg["display_recipient"] == zulip_cfg["stream"] | ||
at_the_specified_subject = msg["subject"] == zulip_cfg["subject"] | ||
if isa_stream and not_from_bot and in_the_specified_stream and at_the_specified_subject: | ||
rocket_username = zulip_to_rocket_username(msg["sender_full_name"], site_without_http) | ||
rocket_text = "{0}: {1}".format(rocket_username, | ||
msg["content"]) | ||
pprint(rocket_client.chat_post_message(rocket_text, channel=rocket_cfg["channel_id"]).json()) | ||
return _zulip_to_rocket | ||
|
||
def rocket_listener(rocket_client: RocketChat, zulip_client: zulip.Client, cfg: Cfg): | ||
interval = 2 | ||
rocket_cfg = cfg["rocket"] | ||
while True: | ||
now = datetime.datetime.utcnow() | ||
oldest = now - datetime.timedelta(seconds=interval) | ||
oldest_str = str(oldest)[:-3] + 'Z' | ||
res = rocket_client.channels_history(rocket_cfg['channel_id'], | ||
oldest=oldest_str).json() | ||
rocket_to_zulip(zulip_client, cfg, res) | ||
time.sleep(interval) | ||
|
||
if __name__ == '__main__': | ||
signal.signal(signal.SIGINT, die) | ||
logging.basicConfig(level=logging.WARNING) | ||
|
||
# Get config for each clients | ||
zulip_config = config["zulip"] | ||
rocket_config = config["rocket"] | ||
|
||
# Initiate clients | ||
print("Starting rocketchat mirroring bot") | ||
|
||
backoff = zulip.RandomExponentialBackoff(timeout_success_equivalent=300) | ||
while backoff.keep_going(): | ||
try: | ||
zulip_client = zulip.Client(email=zulip_config["email"], | ||
api_key=zulip_config["api_key"], | ||
site=zulip_config["site"]) | ||
|
||
rocket_client = RocketChat(rocket_config['username'], | ||
rocket_config['password'], | ||
server_url=rocket_config['server_url']) | ||
|
||
# A bidirectional mirror | ||
|
||
# returns configured function handler | ||
zulip_message_handler = zulip_to_rocket(rocket_client, config) | ||
p1 = mp.Process(target=zulip_client.call_on_each_message, | ||
args=(zulip_message_handler,)) | ||
|
||
p2 = mp.Process(target=rocket_listener, | ||
args=(rocket_client, zulip_client, config)) | ||
|
||
print("Starting message handler on Zulip client") | ||
p1.start() | ||
print("Starting message handler on Rocket.Chat client") | ||
p2.start() | ||
|
||
p1.join() | ||
p2.join() | ||
|
||
except Exception: | ||
traceback.print_exc() | ||
backoff.fail() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
config = { | ||
"rocket": { | ||
"server_url": "https://chat.serverurl.org", | ||
"username": "botusername", | ||
"password": "botpassword", | ||
"channel_id": "idofthechannel", | ||
"channel_name": "nameofthechannel" | ||
}, | ||
"zulip": { | ||
"email": "[email protected]", | ||
"api_key": "someapikey", | ||
"site": "https://chat.someserver.org", | ||
"stream": "somestream", | ||
"subject": "somesubject" | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more idiomatic way to handle two forever-running subprocesses?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there is only if one of the client has the feature to be non-blocking. So far, only Matrix does: https://github.com/rht/python-zulip-api/blob/b94bf2a31325243f0c4e5402087f17fca77da79e/zulip/integrations/matrix/matrix_bridge.py#L94.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
zulip_client.call_on_each_message
can be made non-blocking, then no multiprocessing is necessary.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually have no problem with these being multiprocess, since the two processes are essentially independent and don't require any coordination. I'm more concerned about tearing them down, so it might make sense to use something like supervisord.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm concerned with the zombie processes even with
p.join()
. To remain lightweight, what about using multithreading like the Matrix client does[1]?[1] https://github.com/matrix-org/matrix-python-sdk/blob/cb815ad96c60573f189ead02037f4d1207554717/matrix_client/client.py#L409-L414
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually prefer multiple processes here.