mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			298 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from collections.abc import Mapping
 | 
						|
 | 
						|
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) -> tuple[str, str] | None:
 | 
						|
    proper_action = get_proper_action(payload, action_type)
 | 
						|
    if proper_action is not None:
 | 
						|
        return get_topic(payload), get_body(payload, proper_action)
 | 
						|
    return None
 | 
						|
 | 
						|
 | 
						|
def get_proper_action(payload: WildValue, action_type: str) -> str | None:
 | 
						|
    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_topic(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,
 | 
						|
}
 |