From 65bfa726ca7af983ee58a93b78d3c922ff2cd617 Mon Sep 17 00:00:00 2001 From: Midgard Date: Mon, 15 Mar 2021 01:11:50 +0100 Subject: [PATCH] Add sending, start work on --follow --- .gitignore | 3 +++ mmcli.py | 77 ++++++++++++++++++++++++++++++++++++++---------------- mmws.py | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 .gitignore create mode 100644 mmws.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bbe7b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +*.pyo diff --git a/mmcli.py b/mmcli.py index 9983c69..35dae2c 100755 --- a/mmcli.py +++ b/mmcli.py @@ -10,8 +10,10 @@ import time import json from typing import Dict, Set, Generator, Optional, NamedTuple import mattermost -import mattermost.ws import re +from threading import Lock + +from mmws import MMws class NotFound(Exception): @@ -31,6 +33,14 @@ def yes_no(x): return "yes" if x else "no" +def http_to_ws(url): + """ + Transform url from http to ws and https to wss + """ + assert url.startswith("http://") or url.startswith("https://") + return "ws" + url[4:] + + def get_posts_for_channel(self, channel_id: str, progress=lambda x: None, **kwargs) -> Generator[Dict, None, None]: """ @raises ApiException: Passed on from lower layers. @@ -117,30 +127,50 @@ def login(mm_api, parsed): def cat(mm_api: mattermost.MMApi, parsed): - channels = [ - resolve_team_channel(mm_api, query) - for query in parsed.channels - ] + # channels = [ + # resolve_team_channel(mm_api, query) + # for query in parsed.channels + # ] + team, channel = resolve_team_channel(mm_api, parsed.channel) users = list(mm_api.get_users()) + if not parsed.ids: + def attribute(key_value): + key, value = key_value + if key == "channel_id": + assert value == channel["id"] + return "channel", channel["name"] + if key == "user_id": + return "username", first(u["username"] for u in users if u["id"] == value) + return key_value + else: + def attribute(key_value): + return key_value - for team, channel in channels: - if not parsed.ids: - def attribute(key_value): - key, value = key_value - if key == "channel_id": - assert value == channel["id"] - return "channel", channel["name"] - if key == "user_id": - return "username", first(u["username"] for u in users if u["id"] == value) - return key_value - else: - def attribute(key_value): - return key_value + # backlog = [] + # backlog_lock = Lock() + if parsed.follow: + raise NotImplementedError("--follow is not yet supported") + # def webs_handler(mmws, event_data): + # if event_data["event"] == "posted": + # with backlog_lock: + # if backlog is not None: + # backlog.append(event_data["data"]) + # return + # print(post_str(attribute, event_data["data"], parsed)) - posts = get_posts_for_channel(mm_api, channel["id"], after=parsed.after) - for post in posts: - print(post_str(attribute, post, parsed)) + # ws_url = http_to_ws(mm_api._url) + "/v4/websocket" + # MMws(webs_handler, mm_api, ws_url) + # return + + posts = get_posts_for_channel(mm_api, channel["id"], after=parsed.after) + for post in posts: + print(post_str(attribute, post, parsed)) + + # with backlog_lock: + # for post in backlog: + # print(post_str(attribute, post, parsed)) + # backlog = None def send(mm_api: mattermost.MMApi, parsed): @@ -245,7 +275,7 @@ Hint: JSON output can be filtered on the command line with jq(1). # --- parser_cat.add_argument("--after", help="all after post with ID") parser_cat.add_argument("--since", help="all after timestamp") - parser_cat.add_argument("-f", "--follow", help="keep running, printing new posts as they come in") + parser_cat.add_argument("-f", "--follow", action="store_true", help="keep running, printing new posts as they come in") parser_send = subparsers.add_parser("send", help="send message(s)") parser_send.add_argument( @@ -268,7 +298,8 @@ Hint: JSON output can be filtered on the command line with jq(1). f"`{prog_name} {parsed.action}` requires access token; get one with `{prog_name} login` " f"and set environment variable {ENVVAR_ACCESSTOKEN}") - mm_api = mattermost.MMApi(f"https://{parsed.server}/api") + server = parsed.server if re.match(r"^[a-z]+://", parsed.server) else f"https://{parsed.server}" + mm_api = mattermost.MMApi(f"{server}/api") if access_token: mm_api._headers.update({"Authorization": f"Bearer {access_token}"}) diff --git a/mmws.py b/mmws.py new file mode 100644 index 0000000..cf5531e --- /dev/null +++ b/mmws.py @@ -0,0 +1,52 @@ +import sys +import json +import threading +import websocket + + +websocket.enableTrace(True) + +class MMws: + """ + Websocket client. + """ + + def __init__(self, ws_handler, api, ws_url): + self.api = api + self.ws_url = ws_url + self.ws_handler = ws_handler + + self.ws_app = None + + self.thread = threading.Thread(target=self._open_websocket) + self.thread.setName("websocket") + self.thread.setDaemon(False) + self.thread.start() + + + def _open_websocket(self): + def on_open(ws): + print("Opened") + ws.send(json.dumps({ + "seq": 1, "action": "authentication_challenge", "data": {"token": self.api._bearer} + })) + + def on_message(ws, msg): + print(msg) + self.ws_handler(self, msg) + + def on_error(ws, error): + print(error, file=sys.stderr) + sys.exit(1) + + self.ws_app = websocket.WebSocketApp(self.ws_url, on_open=on_open, on_message=on_message, + on_close=lambda ws: print("Closed")) + print("Start", flush=True) + self.ws_app.run_forever() + print("Done", flush=True) + + + + def close_websocket(self): + self.ws_app.close() + self.thread.join()