integrations.slack.parser

integrations/slack/parser.py

  1"""
  2integrations/slack/parser.py
  3"""
  4
  5import logging
  6from typing import TYPE_CHECKING, Any, cast
  7
  8import libs.global_value as g
  9from integrations.base.interface import MessageParserDataMixin, MessageParserInterface
 10from integrations.protocols import MsgData, PostData, StatusData
 11from libs.types import ChannelType, CommandType, MessageStatus
 12
 13if TYPE_CHECKING:
 14    from integrations.slack.adapter import ServiceAdapter
 15
 16
 17class MessageParser(MessageParserDataMixin, MessageParserInterface):
 18    """メッセージ解析クラス"""
 19
 20    def __init__(self) -> None:
 21        MessageParserDataMixin.__init__(self)
 22        self.data: MsgData = MsgData()
 23        self.post: PostData = PostData()
 24        self.status: StatusData = StatusData()
 25
 26    def parser(self, _body: dict[str, Any]) -> None:
 27        g.adapter = cast("ServiceAdapter", g.adapter)
 28        _event = cast(dict[str, Any], _body.get("event", _body))  # 対象のevent抽出
 29
 30        if _body.get("command") == g.adapter.conf.slash_command:  # スラッシュコマンド
 31            self.status.command_flg = True
 32            self.status.command_name = g.adapter.conf.slash_command
 33            self.data.status = MessageStatus.APPEND
 34            self.data.user_id = str(_body.get("user_id", ""))
 35            if _body.get("channel_name") == "directmessage":
 36                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 37                self.data.channel_id = str(_body.get("channel_id", ""))
 38            else:  # チャンネル内コマンド
 39                if channel_id := str(_body.get("channel_id")):
 40                    self.status.source = f"slack_{channel_id}"
 41                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 42                self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)  # DM Open
 43        elif _body.get("container"):  # Homeタブ
 44            self.data.user_id = str(cast(dict[str, Any], _body["user"]).get("id", ""))
 45            self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)
 46            self.data.channel_type = ChannelType.HOME_APP
 47            self.data.text = "dummy"
 48        elif _body.get("iid"):  # 検索結果
 49            if _channel_id := str(cast(dict[str, Any], _event["channel"]).get("id", "")):
 50                self.data.channel_id = _channel_id
 51                _event.pop("channel")
 52            self.data.channel_type = ChannelType.SEARCH
 53            self.data.status = MessageStatus.APPEND
 54        else:
 55            match _event.get("subtype"):
 56                case subtype if str(subtype).endswith(("_topic", "_purpose", "_name")):
 57                    self.data.status = MessageStatus.DO_NOTHING
 58                    return
 59                case "message_changed":
 60                    self.data.status = MessageStatus.CHANGED
 61                    _event.update(cast(dict[str, Any], _event["message"]))
 62                    if cast(dict[str, Any], _event["message"]).get("subtype") == "tombstone":  # スレッド元の削除
 63                        self.data.status = MessageStatus.DELETED
 64                    elif _edited := cast(dict[str, Any], _event["message"]).get("edited"):
 65                        self.data.edited_ts = str(cast(dict[str, Any], _edited).get("ts", "undetermined"))
 66                    if _previous_message := cast(dict[str, Any], _event.get("previous_message", {})):
 67                        if _previous_message.get("thread_ts"):
 68                            _event.update(thread_ts=_previous_message.get("thread_ts", "0"))
 69                        if cast(dict[str, Any], _event["message"]).get("text") == _previous_message.get("text"):
 70                            self.data.status = MessageStatus.DO_NOTHING
 71                case "message_deleted":
 72                    self.data.status = MessageStatus.DELETED
 73                    _event.update(cast(dict[str, Any], _event["previous_message"]))
 74                case "file_share" | "thread_broadcast":
 75                    self.data.status = MessageStatus.APPEND
 76                case None:
 77                    self.data.status = MessageStatus.APPEND
 78                case _:
 79                    self.data.status = MessageStatus.APPEND
 80                    logging.warning("unknown subtype: %s", _body)
 81
 82        match _event.get("channel_type"):
 83            case "directmessage" | "im":
 84                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 85            case "channel":
 86                self.data.channel_type = ChannelType.CHANNEL
 87            case "group":
 88                self.data.channel_type = ChannelType.PRIVATE
 89            case _:
 90                pass
 91
 92        self.data.text = _event.get("text", self.data.text)
 93        self.data.channel_id = _event.get("channel", self.data.channel_id)
 94        self.data.user_id = _event.get("user", self.data.user_id)
 95        self.data.event_ts = _event.get("ts", "0")
 96        self.data.thread_ts = _event.get("thread_ts", "0")
 97        if not self.status.source:
 98            self.status.source = f"slack_{self.data.channel_id}"
 99
100        logging.debug(self.data)
101
102    @property
103    def in_thread(self) -> bool:
104        """スレッド内のメッセージか判定"""
105        if self.data.thread_ts == "0":
106            return False
107        if self.data.event_ts == self.data.thread_ts:
108            return False
109        return True
110
111    @property
112    def is_bot(self) -> bool:
113        if self.data.user_id == "USLACKBOT":
114            return True
115        return False
116
117    @property
118    def check_updatable(self) -> bool:
119        g.adapter = cast("ServiceAdapter", g.adapter)
120        ret: bool = False
121
122        # 突合処理中はチェック省略
123        if self.status.command_type == CommandType.COMPARISON:
124            return True
125
126        if g.adapter.conf.channel_limitations:
127            if self.data.channel_id in g.adapter.conf.channel_limitations:
128                ret = True
129        else:  # リストが空なら全チャンネルが対象
130            match self.data.channel_type:
131                case ChannelType.CHANNEL:  # public channel
132                    ret = True
133                case ChannelType.PRIVATE:  # private channel
134                    ret = True
135                case ChannelType.HOME_APP:  # home app
136                    ret = True
137                case ChannelType.DIRECT_MESSAGE:  # direct message
138                    ret = False
139                case ChannelType.SEARCH:
140                    ret = True
141                case _:
142                    pass
143
144        return ret
145
146    @property
147    def ignore_user(self) -> bool:
148        g.adapter = cast("ServiceAdapter", g.adapter)
149        return self.data.user_id in g.adapter.conf.ignore_userid
 18class MessageParser(MessageParserDataMixin, MessageParserInterface):
 19    """メッセージ解析クラス"""
 20
 21    def __init__(self) -> None:
 22        MessageParserDataMixin.__init__(self)
 23        self.data: MsgData = MsgData()
 24        self.post: PostData = PostData()
 25        self.status: StatusData = StatusData()
 26
 27    def parser(self, _body: dict[str, Any]) -> None:
 28        g.adapter = cast("ServiceAdapter", g.adapter)
 29        _event = cast(dict[str, Any], _body.get("event", _body))  # 対象のevent抽出
 30
 31        if _body.get("command") == g.adapter.conf.slash_command:  # スラッシュコマンド
 32            self.status.command_flg = True
 33            self.status.command_name = g.adapter.conf.slash_command
 34            self.data.status = MessageStatus.APPEND
 35            self.data.user_id = str(_body.get("user_id", ""))
 36            if _body.get("channel_name") == "directmessage":
 37                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 38                self.data.channel_id = str(_body.get("channel_id", ""))
 39            else:  # チャンネル内コマンド
 40                if channel_id := str(_body.get("channel_id")):
 41                    self.status.source = f"slack_{channel_id}"
 42                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 43                self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)  # DM Open
 44        elif _body.get("container"):  # Homeタブ
 45            self.data.user_id = str(cast(dict[str, Any], _body["user"]).get("id", ""))
 46            self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)
 47            self.data.channel_type = ChannelType.HOME_APP
 48            self.data.text = "dummy"
 49        elif _body.get("iid"):  # 検索結果
 50            if _channel_id := str(cast(dict[str, Any], _event["channel"]).get("id", "")):
 51                self.data.channel_id = _channel_id
 52                _event.pop("channel")
 53            self.data.channel_type = ChannelType.SEARCH
 54            self.data.status = MessageStatus.APPEND
 55        else:
 56            match _event.get("subtype"):
 57                case subtype if str(subtype).endswith(("_topic", "_purpose", "_name")):
 58                    self.data.status = MessageStatus.DO_NOTHING
 59                    return
 60                case "message_changed":
 61                    self.data.status = MessageStatus.CHANGED
 62                    _event.update(cast(dict[str, Any], _event["message"]))
 63                    if cast(dict[str, Any], _event["message"]).get("subtype") == "tombstone":  # スレッド元の削除
 64                        self.data.status = MessageStatus.DELETED
 65                    elif _edited := cast(dict[str, Any], _event["message"]).get("edited"):
 66                        self.data.edited_ts = str(cast(dict[str, Any], _edited).get("ts", "undetermined"))
 67                    if _previous_message := cast(dict[str, Any], _event.get("previous_message", {})):
 68                        if _previous_message.get("thread_ts"):
 69                            _event.update(thread_ts=_previous_message.get("thread_ts", "0"))
 70                        if cast(dict[str, Any], _event["message"]).get("text") == _previous_message.get("text"):
 71                            self.data.status = MessageStatus.DO_NOTHING
 72                case "message_deleted":
 73                    self.data.status = MessageStatus.DELETED
 74                    _event.update(cast(dict[str, Any], _event["previous_message"]))
 75                case "file_share" | "thread_broadcast":
 76                    self.data.status = MessageStatus.APPEND
 77                case None:
 78                    self.data.status = MessageStatus.APPEND
 79                case _:
 80                    self.data.status = MessageStatus.APPEND
 81                    logging.warning("unknown subtype: %s", _body)
 82
 83        match _event.get("channel_type"):
 84            case "directmessage" | "im":
 85                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 86            case "channel":
 87                self.data.channel_type = ChannelType.CHANNEL
 88            case "group":
 89                self.data.channel_type = ChannelType.PRIVATE
 90            case _:
 91                pass
 92
 93        self.data.text = _event.get("text", self.data.text)
 94        self.data.channel_id = _event.get("channel", self.data.channel_id)
 95        self.data.user_id = _event.get("user", self.data.user_id)
 96        self.data.event_ts = _event.get("ts", "0")
 97        self.data.thread_ts = _event.get("thread_ts", "0")
 98        if not self.status.source:
 99            self.status.source = f"slack_{self.data.channel_id}"
100
101        logging.debug(self.data)
102
103    @property
104    def in_thread(self) -> bool:
105        """スレッド内のメッセージか判定"""
106        if self.data.thread_ts == "0":
107            return False
108        if self.data.event_ts == self.data.thread_ts:
109            return False
110        return True
111
112    @property
113    def is_bot(self) -> bool:
114        if self.data.user_id == "USLACKBOT":
115            return True
116        return False
117
118    @property
119    def check_updatable(self) -> bool:
120        g.adapter = cast("ServiceAdapter", g.adapter)
121        ret: bool = False
122
123        # 突合処理中はチェック省略
124        if self.status.command_type == CommandType.COMPARISON:
125            return True
126
127        if g.adapter.conf.channel_limitations:
128            if self.data.channel_id in g.adapter.conf.channel_limitations:
129                ret = True
130        else:  # リストが空なら全チャンネルが対象
131            match self.data.channel_type:
132                case ChannelType.CHANNEL:  # public channel
133                    ret = True
134                case ChannelType.PRIVATE:  # private channel
135                    ret = True
136                case ChannelType.HOME_APP:  # home app
137                    ret = True
138                case ChannelType.DIRECT_MESSAGE:  # direct message
139                    ret = False
140                case ChannelType.SEARCH:
141                    ret = True
142                case _:
143                    pass
144
145        return ret
146
147    @property
148    def ignore_user(self) -> bool:
149        g.adapter = cast("ServiceAdapter", g.adapter)
150        return self.data.user_id in g.adapter.conf.ignore_userid

メッセージ解析クラス

def parser(self, _body: dict[str, typing.Any]) -> None:
 27    def parser(self, _body: dict[str, Any]) -> None:
 28        g.adapter = cast("ServiceAdapter", g.adapter)
 29        _event = cast(dict[str, Any], _body.get("event", _body))  # 対象のevent抽出
 30
 31        if _body.get("command") == g.adapter.conf.slash_command:  # スラッシュコマンド
 32            self.status.command_flg = True
 33            self.status.command_name = g.adapter.conf.slash_command
 34            self.data.status = MessageStatus.APPEND
 35            self.data.user_id = str(_body.get("user_id", ""))
 36            if _body.get("channel_name") == "directmessage":
 37                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 38                self.data.channel_id = str(_body.get("channel_id", ""))
 39            else:  # チャンネル内コマンド
 40                if channel_id := str(_body.get("channel_id")):
 41                    self.status.source = f"slack_{channel_id}"
 42                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 43                self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)  # DM Open
 44        elif _body.get("container"):  # Homeタブ
 45            self.data.user_id = str(cast(dict[str, Any], _body["user"]).get("id", ""))
 46            self.data.channel_id = g.adapter.functions.get_dm_channel_id(self.data.user_id)
 47            self.data.channel_type = ChannelType.HOME_APP
 48            self.data.text = "dummy"
 49        elif _body.get("iid"):  # 検索結果
 50            if _channel_id := str(cast(dict[str, Any], _event["channel"]).get("id", "")):
 51                self.data.channel_id = _channel_id
 52                _event.pop("channel")
 53            self.data.channel_type = ChannelType.SEARCH
 54            self.data.status = MessageStatus.APPEND
 55        else:
 56            match _event.get("subtype"):
 57                case subtype if str(subtype).endswith(("_topic", "_purpose", "_name")):
 58                    self.data.status = MessageStatus.DO_NOTHING
 59                    return
 60                case "message_changed":
 61                    self.data.status = MessageStatus.CHANGED
 62                    _event.update(cast(dict[str, Any], _event["message"]))
 63                    if cast(dict[str, Any], _event["message"]).get("subtype") == "tombstone":  # スレッド元の削除
 64                        self.data.status = MessageStatus.DELETED
 65                    elif _edited := cast(dict[str, Any], _event["message"]).get("edited"):
 66                        self.data.edited_ts = str(cast(dict[str, Any], _edited).get("ts", "undetermined"))
 67                    if _previous_message := cast(dict[str, Any], _event.get("previous_message", {})):
 68                        if _previous_message.get("thread_ts"):
 69                            _event.update(thread_ts=_previous_message.get("thread_ts", "0"))
 70                        if cast(dict[str, Any], _event["message"]).get("text") == _previous_message.get("text"):
 71                            self.data.status = MessageStatus.DO_NOTHING
 72                case "message_deleted":
 73                    self.data.status = MessageStatus.DELETED
 74                    _event.update(cast(dict[str, Any], _event["previous_message"]))
 75                case "file_share" | "thread_broadcast":
 76                    self.data.status = MessageStatus.APPEND
 77                case None:
 78                    self.data.status = MessageStatus.APPEND
 79                case _:
 80                    self.data.status = MessageStatus.APPEND
 81                    logging.warning("unknown subtype: %s", _body)
 82
 83        match _event.get("channel_type"):
 84            case "directmessage" | "im":
 85                self.data.channel_type = ChannelType.DIRECT_MESSAGE
 86            case "channel":
 87                self.data.channel_type = ChannelType.CHANNEL
 88            case "group":
 89                self.data.channel_type = ChannelType.PRIVATE
 90            case _:
 91                pass
 92
 93        self.data.text = _event.get("text", self.data.text)
 94        self.data.channel_id = _event.get("channel", self.data.channel_id)
 95        self.data.user_id = _event.get("user", self.data.user_id)
 96        self.data.event_ts = _event.get("ts", "0")
 97        self.data.thread_ts = _event.get("thread_ts", "0")
 98        if not self.status.source:
 99            self.status.source = f"slack_{self.data.channel_id}"
100
101        logging.debug(self.data)

メッセージ解析

Arguments:
  • body (Any): 解析データ
in_thread: bool
103    @property
104    def in_thread(self) -> bool:
105        """スレッド内のメッセージか判定"""
106        if self.data.thread_ts == "0":
107            return False
108        if self.data.event_ts == self.data.thread_ts:
109            return False
110        return True

スレッド内のメッセージか判定

is_bot: bool
112    @property
113    def is_bot(self) -> bool:
114        if self.data.user_id == "USLACKBOT":
115            return True
116        return False

botのポストかチェック

Returns:

bool: 真偽値

  • True: botのポスト
  • False: ユーザのポスト
check_updatable: bool
118    @property
119    def check_updatable(self) -> bool:
120        g.adapter = cast("ServiceAdapter", g.adapter)
121        ret: bool = False
122
123        # 突合処理中はチェック省略
124        if self.status.command_type == CommandType.COMPARISON:
125            return True
126
127        if g.adapter.conf.channel_limitations:
128            if self.data.channel_id in g.adapter.conf.channel_limitations:
129                ret = True
130        else:  # リストが空なら全チャンネルが対象
131            match self.data.channel_type:
132                case ChannelType.CHANNEL:  # public channel
133                    ret = True
134                case ChannelType.PRIVATE:  # private channel
135                    ret = True
136                case ChannelType.HOME_APP:  # home app
137                    ret = True
138                case ChannelType.DIRECT_MESSAGE:  # direct message
139                    ret = False
140                case ChannelType.SEARCH:
141                    ret = True
142                case _:
143                    pass
144
145        return ret

DB操作の許可チェック

Returns:

bool: 真偽値

  • True: 許可
  • False: 禁止
ignore_user: bool
147    @property
148    def ignore_user(self) -> bool:
149        g.adapter = cast("ServiceAdapter", g.adapter)
150        return self.data.user_id in g.adapter.conf.ignore_userid

ignore_useridに存在するユーザかチェック

Returns:

bool: 真偽値

  • True: 存在する(操作禁止ユーザ)
  • False: 存在しない