2018-05-16 19:28:39 +02:00
|
|
|
import json
|
2020-06-11 00:54:34 +02:00
|
|
|
import re
|
2024-07-12 02:30:23 +02:00
|
|
|
from typing import Any
|
2018-05-16 19:28:39 +02:00
|
|
|
|
2020-11-24 12:31:28 +01:00
|
|
|
from zerver.lib.message import SendMessageRequest
|
2021-01-31 18:23:48 +01:00
|
|
|
from zerver.models import Message, SubMessage
|
2018-05-16 19:28:39 +02:00
|
|
|
|
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
def get_widget_data(content: str) -> tuple[str | None, Any]:
|
2021-02-12 08:20:45 +01:00
|
|
|
valid_widget_types = ["poll", "todo"]
|
2023-09-05 19:25:40 +02:00
|
|
|
tokens = re.split(r"\s+|\n+", content)
|
2018-06-28 21:17:55 +02:00
|
|
|
|
2018-08-23 14:51:17 +02:00
|
|
|
# tokens[0] will always exist
|
2021-02-12 08:20:45 +01:00
|
|
|
if tokens[0].startswith("/"):
|
2024-09-03 19:42:14 +02:00
|
|
|
widget_type = tokens[0].removeprefix("/")
|
2018-06-28 21:17:55 +02:00
|
|
|
if widget_type in valid_widget_types:
|
2022-08-10 21:00:06 +02:00
|
|
|
remaining_content = content.replace(tokens[0], "", 1)
|
2019-01-28 18:37:28 +01:00
|
|
|
extra_data = get_extra_data_from_widget_type(remaining_content, widget_type)
|
2018-06-28 21:17:55 +02:00
|
|
|
return widget_type, extra_data
|
|
|
|
|
|
|
|
return None, None
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-07-29 04:25:31 +02:00
|
|
|
def parse_poll_extra_data(content: str) -> Any:
|
|
|
|
# This is used to extract the question from the poll command.
|
|
|
|
# The command '/poll question' will pre-set the question in the poll
|
|
|
|
lines = content.splitlines()
|
|
|
|
question = ""
|
|
|
|
options = []
|
|
|
|
if lines and lines[0]:
|
|
|
|
question = lines.pop(0).strip()
|
|
|
|
for line in lines:
|
|
|
|
# If someone is using the list syntax, we remove it
|
|
|
|
# before adding an option.
|
|
|
|
option = re.sub(r"(\s*[-*]?\s*)", "", line.strip(), count=1)
|
|
|
|
if len(option) > 0:
|
|
|
|
options.append(option)
|
|
|
|
extra_data = {
|
|
|
|
"question": question,
|
|
|
|
"options": options,
|
|
|
|
}
|
|
|
|
return extra_data
|
|
|
|
|
|
|
|
|
|
|
|
def parse_todo_extra_data(content: str) -> Any:
|
|
|
|
# This is used to extract the task list title from the todo command.
|
|
|
|
# The command '/todo Title' will pre-set the task list title
|
|
|
|
lines = content.splitlines()
|
|
|
|
task_list_title = ""
|
|
|
|
if lines and lines[0]:
|
|
|
|
task_list_title = lines.pop(0).strip()
|
|
|
|
tasks = []
|
|
|
|
for line in lines:
|
|
|
|
# If someone is using the list syntax, we remove it
|
|
|
|
# before adding a task.
|
|
|
|
task_data = re.sub(r"(\s*[-*]?\s*)", "", line.strip(), count=1)
|
|
|
|
if len(task_data) > 0:
|
|
|
|
# a task and its description (optional) are separated
|
2024-04-30 18:58:27 +02:00
|
|
|
# by the (first) `: ` substring
|
|
|
|
task_data_array = task_data.split(": ", 1)
|
2022-07-29 04:25:31 +02:00
|
|
|
tasks.append(
|
|
|
|
{
|
|
|
|
"task": task_data_array[0].strip(),
|
|
|
|
"desc": task_data_array[1].strip() if len(task_data_array) > 1 else "",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
extra_data = {
|
|
|
|
"task_list_title": task_list_title,
|
|
|
|
"tasks": tasks,
|
|
|
|
}
|
|
|
|
return extra_data
|
|
|
|
|
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
def get_extra_data_from_widget_type(content: str, widget_type: str | None) -> Any:
|
2021-02-12 08:20:45 +01:00
|
|
|
if widget_type == "poll":
|
2022-08-04 16:20:54 +02:00
|
|
|
return parse_poll_extra_data(content)
|
2022-07-29 04:25:31 +02:00
|
|
|
else:
|
2022-08-04 16:20:54 +02:00
|
|
|
return parse_todo_extra_data(content)
|
2018-06-28 21:17:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-11-24 12:31:28 +01:00
|
|
|
def do_widget_post_save_actions(send_request: SendMessageRequest) -> None:
|
2021-02-12 08:19:30 +01:00
|
|
|
"""
|
2021-05-14 00:16:30 +02:00
|
|
|
This code works with the web app; mobile and other
|
2019-01-29 16:57:45 +01:00
|
|
|
clients should also start supporting this soon.
|
2021-02-12 08:19:30 +01:00
|
|
|
"""
|
2020-11-24 12:31:28 +01:00
|
|
|
message_content = send_request.message.content
|
|
|
|
sender_id = send_request.message.sender_id
|
|
|
|
message_id = send_request.message.id
|
2018-05-16 19:28:39 +02:00
|
|
|
|
|
|
|
widget_type = None
|
|
|
|
extra_data = None
|
|
|
|
|
2020-11-24 11:35:24 +01:00
|
|
|
widget_type, extra_data = get_widget_data(message_content)
|
2020-11-24 12:31:28 +01:00
|
|
|
widget_content = send_request.widget_content
|
2018-05-21 15:23:46 +02:00
|
|
|
if widget_content is not None:
|
|
|
|
# Note that we validate this data in check_message,
|
|
|
|
# so we can trust it here.
|
2021-02-12 08:20:45 +01:00
|
|
|
widget_type = widget_content["widget_type"]
|
|
|
|
extra_data = widget_content["extra_data"]
|
2018-05-21 15:23:46 +02:00
|
|
|
|
2018-05-16 19:28:39 +02:00
|
|
|
if widget_type:
|
|
|
|
content = dict(
|
|
|
|
widget_type=widget_type,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
extra_data=extra_data,
|
2018-05-16 19:28:39 +02:00
|
|
|
)
|
|
|
|
submessage = SubMessage(
|
|
|
|
sender_id=sender_id,
|
|
|
|
message_id=message_id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="widget",
|
2018-05-16 19:28:39 +02:00
|
|
|
content=json.dumps(content),
|
|
|
|
)
|
|
|
|
submessage.save()
|
2020-11-24 12:31:28 +01:00
|
|
|
send_request.submessages = SubMessage.get_raw_db_rows([message_id])
|
2021-01-31 18:23:48 +01:00
|
|
|
|
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
def get_widget_type(*, message_id: int) -> str | None:
|
2021-06-13 17:00:45 +02:00
|
|
|
submessage = (
|
|
|
|
SubMessage.objects.filter(
|
|
|
|
message_id=message_id,
|
|
|
|
msg_type="widget",
|
|
|
|
)
|
|
|
|
.only("content")
|
|
|
|
.first()
|
|
|
|
)
|
|
|
|
|
|
|
|
if submessage is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
data = json.loads(submessage.content)
|
|
|
|
except Exception:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return data["widget_type"]
|
|
|
|
except Exception:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2021-01-31 18:23:48 +01:00
|
|
|
def is_widget_message(message: Message) -> bool:
|
|
|
|
# Right now all messages that are widgetized use submessage, and vice versa.
|
|
|
|
return message.submessage_set.exists()
|