mirror of
https://github.com/zulip/zulip.git
synced 2025-11-12 09:58:06 +00:00
Black 23 enforces some slightly more specific rules about empty line counts and redundant parenthesis removal, but the result is still compatible with Black 22. (This does not actually upgrade our Python environment to Black 23 yet.) Signed-off-by: Anders Kaseorg <anders@zulip.com>
298 lines
11 KiB
Python
298 lines
11 KiB
Python
from typing import Mapping, Optional, Tuple
|
|
|
|
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
|
|
from zerver.lib.validator import WildValue, check_bool, check_none_or, check_string
|
|
|
|
SUPPORTED_CARD_ACTIONS = [
|
|
"updateCard",
|
|
"createCard",
|
|
"addLabelToCard",
|
|
"removeLabelFromCard",
|
|
"addMemberToCard",
|
|
"removeMemberFromCard",
|
|
"addAttachmentToCard",
|
|
"addChecklistToCard",
|
|
"commentCard",
|
|
"updateCheckItemStateOnCard",
|
|
]
|
|
|
|
IGNORED_CARD_ACTIONS = [
|
|
"copyCard",
|
|
"createCheckItem",
|
|
"updateCheckItem",
|
|
"updateList",
|
|
]
|
|
|
|
CREATE = "createCard"
|
|
CHANGE_LIST = "changeList"
|
|
CHANGE_NAME = "changeName"
|
|
SET_DESC = "setDesc"
|
|
CHANGE_DESC = "changeDesc"
|
|
REMOVE_DESC = "removeDesc"
|
|
ARCHIVE = "archiveCard"
|
|
REOPEN = "reopenCard"
|
|
SET_DUE_DATE = "setDueDate"
|
|
CHANGE_DUE_DATE = "changeDueDate"
|
|
REMOVE_DUE_DATE = "removeDueDate"
|
|
ADD_LABEL = "addLabelToCard"
|
|
REMOVE_LABEL = "removeLabelFromCard"
|
|
ADD_MEMBER = "addMemberToCard"
|
|
REMOVE_MEMBER = "removeMemberFromCard"
|
|
ADD_ATTACHMENT = "addAttachmentToCard"
|
|
ADD_CHECKLIST = "addChecklistToCard"
|
|
COMMENT = "commentCard"
|
|
UPDATE_CHECK_ITEM_STATE = "updateCheckItemStateOnCard"
|
|
|
|
TRELLO_CARD_URL_TEMPLATE = "[{card_name}]({card_url})"
|
|
|
|
ACTIONS_TO_MESSAGE_MAPPER = {
|
|
CREATE: "created {card_url_template}.",
|
|
CHANGE_LIST: "moved {card_url_template} from {old_list} to {new_list}.",
|
|
CHANGE_NAME: 'renamed the card from "{old_name}" to {card_url_template}.',
|
|
SET_DESC: "set description for {card_url_template} to:\n~~~ quote\n{desc}\n~~~\n",
|
|
CHANGE_DESC: (
|
|
"changed description for {card_url_template} from\n"
|
|
"~~~ quote\n{old_desc}\n~~~\nto\n~~~ quote\n{desc}\n~~~\n"
|
|
),
|
|
REMOVE_DESC: "removed description from {card_url_template}.",
|
|
ARCHIVE: "archived {card_url_template}.",
|
|
REOPEN: "reopened {card_url_template}.",
|
|
SET_DUE_DATE: "set due date for {card_url_template} to {due_date}.",
|
|
CHANGE_DUE_DATE: "changed due date for {card_url_template} from {old_due_date} to {due_date}.",
|
|
REMOVE_DUE_DATE: "removed the due date from {card_url_template}.",
|
|
ADD_LABEL: 'added a {color} label with "{text}" to {card_url_template}.',
|
|
REMOVE_LABEL: 'removed a {color} label with "{text}" from {card_url_template}.',
|
|
ADD_MEMBER: "added {member_name} to {card_url_template}.",
|
|
REMOVE_MEMBER: "removed {member_name} from {card_url_template}.",
|
|
ADD_ATTACHMENT: "added [{attachment_name}]({attachment_url}) to {card_url_template}.",
|
|
ADD_CHECKLIST: "added the {checklist_name} checklist to {card_url_template}.",
|
|
COMMENT: "commented on {card_url_template}:\n~~~ quote\n{text}\n~~~\n",
|
|
UPDATE_CHECK_ITEM_STATE: "{action} **{item_name}** in **{checklist_name}** ({card_url_template}).",
|
|
}
|
|
|
|
|
|
def prettify_date(date_string: str) -> str:
|
|
return date_string.replace("T", " ").replace(".000", "").replace("Z", " UTC")
|
|
|
|
|
|
def process_card_action(payload: WildValue, action_type: str) -> Optional[Tuple[str, str]]:
|
|
proper_action = get_proper_action(payload, action_type)
|
|
if proper_action is not None:
|
|
return get_subject(payload), get_body(payload, proper_action)
|
|
return None
|
|
|
|
|
|
def get_proper_action(payload: WildValue, action_type: str) -> Optional[str]:
|
|
if action_type == "updateCard":
|
|
data = get_action_data(payload)
|
|
old_data = data["old"]
|
|
card_data = data["card"]
|
|
if data.get("listBefore"):
|
|
return CHANGE_LIST
|
|
if old_data.get("name").tame(check_none_or(check_string)):
|
|
return CHANGE_NAME
|
|
if old_data.get("desc").tame(check_none_or(check_string)) == "":
|
|
return SET_DESC
|
|
if old_data.get("desc").tame(check_none_or(check_string)):
|
|
if card_data.get("desc").tame(check_none_or(check_string)) == "":
|
|
return REMOVE_DESC
|
|
else:
|
|
return CHANGE_DESC
|
|
if old_data.get("due", "").tame(check_none_or(check_string)) is None:
|
|
return SET_DUE_DATE
|
|
if old_data.get("due").tame(check_none_or(check_string)):
|
|
if card_data.get("due", "").tame(check_none_or(check_string)) is None:
|
|
return REMOVE_DUE_DATE
|
|
else:
|
|
return CHANGE_DUE_DATE
|
|
if old_data.get("closed").tame(check_none_or(check_bool)) is False and card_data.get(
|
|
"closed", False
|
|
).tame(check_bool):
|
|
return ARCHIVE
|
|
if (
|
|
old_data.get("closed").tame(check_none_or(check_bool))
|
|
and card_data.get("closed").tame(check_none_or(check_bool)) is False
|
|
):
|
|
return REOPEN
|
|
# We don't support events for when a card is moved up or down
|
|
# within a single list (pos), or when the cover changes (cover).
|
|
# We also don't know if "dueComplete" is just a new name for "due".
|
|
ignored_fields = [
|
|
"cover",
|
|
"dueComplete",
|
|
"idAttachmentCover",
|
|
"pos",
|
|
]
|
|
for field in ignored_fields:
|
|
if field in old_data:
|
|
return None
|
|
raise UnsupportedWebhookEventTypeError(action_type)
|
|
|
|
return action_type
|
|
|
|
|
|
def get_subject(payload: WildValue) -> str:
|
|
return get_action_data(payload)["board"]["name"].tame(check_string)
|
|
|
|
|
|
def get_body(payload: WildValue, action_type: str) -> str:
|
|
message_body = ACTIONS_TO_FILL_BODY_MAPPER[action_type](payload, action_type)
|
|
creator = payload["action"]["memberCreator"].get("fullName").tame(check_none_or(check_string))
|
|
return f"{creator} {message_body}"
|
|
|
|
|
|
def get_added_checklist_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"checklist_name": get_action_data(payload)["checklist"]["name"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_update_check_item_body(payload: WildValue, action_type: str) -> str:
|
|
action = get_action_data(payload)
|
|
state = action["checkItem"]["state"].tame(check_string)
|
|
data = {
|
|
"action": "checked" if state == "complete" else "unchecked",
|
|
"checklist_name": action["checklist"]["name"].tame(check_string),
|
|
"item_name": action["checkItem"]["name"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_added_attachment_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"attachment_url": get_action_data(payload)["attachment"]["url"].tame(check_string),
|
|
"attachment_name": get_action_data(payload)["attachment"]["name"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_updated_card_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"card_name": get_card_name(payload),
|
|
"old_list": get_action_data(payload)["listBefore"]["name"].tame(check_string),
|
|
"new_list": get_action_data(payload)["listAfter"]["name"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_renamed_card_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"old_name": get_action_data(payload)["old"]["name"].tame(check_string),
|
|
"new_name": get_action_data(payload)["old"]["name"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_added_label_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"color": get_action_data(payload)["value"].tame(check_string),
|
|
"text": get_action_data(payload)["text"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_managed_member_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"member_name": payload["action"]["member"]["fullName"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_comment_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"text": get_action_data(payload)["text"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_managed_due_date_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"due_date": prettify_date(get_action_data(payload)["card"]["due"].tame(check_string)),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_changed_due_date_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"due_date": prettify_date(get_action_data(payload)["card"]["due"].tame(check_string)),
|
|
"old_due_date": prettify_date(get_action_data(payload)["old"]["due"].tame(check_string)),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_managed_desc_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"desc": get_action_data(payload)["card"]["desc"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_changed_desc_body(payload: WildValue, action_type: str) -> str:
|
|
data = {
|
|
"desc": get_action_data(payload)["card"]["desc"].tame(check_string),
|
|
"old_desc": get_action_data(payload)["old"]["desc"].tame(check_string),
|
|
}
|
|
return fill_appropriate_message_content(payload, action_type, data)
|
|
|
|
|
|
def get_body_by_action_type_without_data(payload: WildValue, action_type: str) -> str:
|
|
return fill_appropriate_message_content(payload, action_type)
|
|
|
|
|
|
def fill_appropriate_message_content(
|
|
payload: WildValue, action_type: str, data: Mapping[str, str] = {}
|
|
) -> str:
|
|
data = dict(data)
|
|
if "card_url_template" not in data:
|
|
data["card_url_template"] = get_filled_card_url_template(payload)
|
|
message_body = get_message_body(action_type)
|
|
return message_body.format(**data)
|
|
|
|
|
|
def get_filled_card_url_template(payload: WildValue) -> str:
|
|
return TRELLO_CARD_URL_TEMPLATE.format(
|
|
card_name=get_card_name(payload), card_url=get_card_url(payload)
|
|
)
|
|
|
|
|
|
def get_card_url(payload: WildValue) -> str:
|
|
return "https://trello.com/c/{}".format(
|
|
get_action_data(payload)["card"]["shortLink"].tame(check_string)
|
|
)
|
|
|
|
|
|
def get_message_body(action_type: str) -> str:
|
|
return ACTIONS_TO_MESSAGE_MAPPER[action_type]
|
|
|
|
|
|
def get_card_name(payload: WildValue) -> str:
|
|
return get_action_data(payload)["card"]["name"].tame(check_string)
|
|
|
|
|
|
def get_action_data(payload: WildValue) -> WildValue:
|
|
return payload["action"]["data"]
|
|
|
|
|
|
ACTIONS_TO_FILL_BODY_MAPPER = {
|
|
CREATE: get_body_by_action_type_without_data,
|
|
CHANGE_LIST: get_updated_card_body,
|
|
CHANGE_NAME: get_renamed_card_body,
|
|
SET_DESC: get_managed_desc_body,
|
|
CHANGE_DESC: get_changed_desc_body,
|
|
REMOVE_DESC: get_body_by_action_type_without_data,
|
|
ARCHIVE: get_body_by_action_type_without_data,
|
|
REOPEN: get_body_by_action_type_without_data,
|
|
SET_DUE_DATE: get_managed_due_date_body,
|
|
CHANGE_DUE_DATE: get_changed_due_date_body,
|
|
REMOVE_DUE_DATE: get_body_by_action_type_without_data,
|
|
ADD_LABEL: get_added_label_body,
|
|
REMOVE_LABEL: get_added_label_body,
|
|
ADD_MEMBER: get_managed_member_body,
|
|
REMOVE_MEMBER: get_managed_member_body,
|
|
ADD_ATTACHMENT: get_added_attachment_body,
|
|
ADD_CHECKLIST: get_added_checklist_body,
|
|
COMMENT: get_comment_body,
|
|
UPDATE_CHECK_ITEM_STATE: get_update_check_item_body,
|
|
}
|