libs.functions.search

libs/functions/search.py

  1"""
  2libs/functions/search.py
  3"""
  4
  5import logging
  6import re
  7from contextlib import closing
  8from typing import cast
  9
 10import libs.global_value as g
 11from cls.score import GameResult
 12from cls.timekit import ExtendedDatetime as ExtDt
 13from cls.types import SlackSearchData
 14from libs.utils import dbutil, formatter, validator
 15
 16SlackSearchDict = dict[str, SlackSearchData]
 17DBSearchDict = dict[str, GameResult]
 18
 19
 20def slack_messages(word: str) -> SlackSearchDict:
 21    """slackログからメッセージを検索して返す
 22
 23    Args:
 24        word (str): 検索するワード
 25
 26    Returns:
 27        SlackSearchDict: 検索した結果
 28    """
 29
 30    # 検索クエリ
 31    after = ExtDt(days=-g.cfg.search.after).format("ymd", "-")
 32    query = f"{word} in:{g.cfg.search.channel} after:{after}"
 33    logging.info("query=%s", query)
 34
 35    # データ取得
 36    response = g.webclient.search_messages(
 37        query=query,
 38        sort="timestamp",
 39        sort_dir="asc",
 40        count=100
 41    )
 42    matches = response["messages"]["matches"]  # 1ページ目
 43    for p in range(2, response["messages"]["paging"]["pages"] + 1):
 44        response = g.webclient.search_messages(
 45            query=query,
 46            sort="timestamp",
 47            sort_dir="asc",
 48            count=100,
 49            page=p
 50        )
 51        matches += response["messages"]["matches"]  # 2ページ目以降
 52
 53    # 必要なデータだけ辞書に格納
 54    data: SlackSearchDict = cast(SlackSearchDict, {})
 55    for x in matches:
 56        if isinstance(x, dict):
 57            data[x["ts"]] = {
 58                "channel_id": str(cast(dict, x["channel"]).get("id", "")),
 59                "user_id": str(x.get("user", "")),
 60                "text": str(x.get("text", "")),
 61            }
 62
 63    return data
 64
 65
 66def get_message_details(matches: SlackSearchDict) -> SlackSearchDict:
 67    """メッセージ詳細情報取得
 68
 69    Args:
 70        matches (SlackSearchDict): 対象データ
 71
 72    Returns:
 73        SlackSearchDict: 詳細情報追加データ
 74    """
 75
 76    # 詳細情報取得
 77    for key, val in matches.items():
 78        res: dict = {}
 79        if isinstance(channel_id := val.get("channel_id"), str):
 80            conversations = g.app.client.conversations_replies(channel=channel_id, ts=key)
 81            if (msg := conversations.get("messages")):
 82                res = cast(dict, msg[0])
 83        else:
 84            continue
 85
 86        if res:
 87            # 各種時間取得
 88            matches[key].update({"event_ts": res.get("ts")})  # イベント発生時間
 89            matches[key].update({"thread_ts": res.get("thread_ts")})  # スレッドの先頭
 90            matches[key].update({"edited_ts": cast(dict, res.get("edited", {})).get("ts")})  # 編集時間
 91            # リアクション取得
 92            reaction_ok, reaction_ng = reactions_list(res)
 93            matches[key].update({"reaction_ok": reaction_ok})
 94            matches[key].update({"reaction_ng": reaction_ng})
 95            # スレッド内フラグ
 96            if val.get("event_ts") == val.get("thread_ts") or val.get("thread_ts") is None:
 97                matches[key].update({"in_thread": False})
 98            else:
 99                matches[key].update({"in_thread": True})
100
101    return matches
102
103
104def for_slack_score() -> SlackSearchDict:
105    """過去ログからスコア記録を検索して返す
106
107    Returns:
108        SlackSearchDict: 検索した結果
109    """
110
111    matches = slack_messages(g.cfg.search.keyword)
112
113    # ゲーム結果の抽出
114    for key in list(matches.keys()):
115        if (detection := validator.pattern(matches[key].get("text", ""))):
116            detection.calc(ts=key)
117            if matches[key].get("user_id", "") in g.cfg.setting.ignore_userid:  # 除外ユーザからのポストは破棄
118                logging.info("skip ignore user: %s (%s)", matches[key]["user_id"], detection)
119                matches.pop(key)
120                continue
121            matches[key]["score"] = detection
122            matches[key].pop("text")
123        else:  # 不一致は破棄
124            matches.pop(key)
125
126    # 結果が無い場合は空の辞書を返して後続の処理をスキップ
127    if not matches:
128        return cast(SlackSearchDict, {})
129
130    matches = get_message_details(matches)
131    g.msg.channel_type = "search_messages"
132    return matches
133
134
135def for_slack_remarks() -> SlackSearchDict:
136    """slackログからメモを検索して返す
137
138    Returns:
139        SlackSearchDict: 検索した結果
140    """
141
142    matches = slack_messages(g.cfg.cw.remarks_word)
143
144    # メモの抽出
145    for key in list(matches.keys()):
146        if re.match(rf"^{g.cfg.cw.remarks_word}", matches[key].get("text", "")):  # キーワードが先頭に存在するかチェック
147            text = matches[key]["text"].replace(g.cfg.cw.remarks_word, "").strip().split()
148            if matches[key].get("user_id", "") in g.cfg.setting.ignore_userid:  # 除外ユーザからのポストは破棄
149                logging.info("skip ignore user: %s, (%s)", matches[key]["user_id"], text)
150                matches.pop(key)
151                continue
152            matches[key]["remarks"] = []
153            g.params.update(unregistered_replace=False)  # 名前ブレを修正(ゲスト無効)
154            for name, matter in zip(text[0::2], text[1::2]):
155                matches[key]["remarks"].append((formatter.name_replace(name, False), matter))
156            matches[key].pop("text")
157        else:  # 不一致は破棄
158            matches.pop(key)
159
160    # 結果が無い場合は空の辞書を返して後続の処理をスキップ
161    if not matches:
162        return cast(SlackSearchDict, {})
163
164    matches = get_message_details(matches)
165    g.msg.channel_type = "search_messages"
166    return matches
167
168
169def for_db_score(first_ts: float | bool = False) -> DBSearchDict:
170    """データベースからスコアを検索して返す
171
172    Args:
173        first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
174
175    Returns:
176        DBSearchDict: 検索した結果
177    """
178
179    if not first_ts:
180        return {}
181
182    data: DBSearchDict = {}
183    with closing(dbutil.get_connection()) as conn:
184        curs = conn.cursor()
185        rows = curs.execute("select * from result where ts >= ?", (str(first_ts),))
186        for row in rows.fetchall():
187            ts = str(dict(row).get("ts", ""))
188            result = GameResult()
189            result.set(**dict(row))
190            data[ts] = result
191
192    return data
193
194
195def for_db_remarks(first_ts: float | bool = False) -> list:
196    """データベースからメモを検索して返す
197
198    Args:
199        first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
200
201    Returns:
202        list: 検索した結果
203    """
204
205    if not first_ts:
206        return []
207
208    # データベースからデータ取得
209    data: list = []
210    with closing(dbutil.get_connection()) as cur:
211        # 記録済みメモ内容
212        rows = cur.execute("select * from remarks where thread_ts>=?", (str(first_ts),))
213        for row in rows.fetchall():
214            data.append({
215                "thread_ts": row["thread_ts"],
216                "event_ts": row["event_ts"],
217                "name": row["name"],
218                "matter": row["matter"],
219            })
220
221    return data
222
223
224def reactions_list(msg: dict) -> tuple[list, list]:
225    """botが付けたリアクションを取得
226
227    Args:
228        msg (dict): メッセージ内容
229
230    Returns:
231        tuple[list, list]:
232        - reaction_ok: okが付いているメッセージのタイムスタンプ
233        - reaction_ng: ngが付いているメッセージのタイムスタンプ
234    """
235
236    reaction_ok: list = []
237    reaction_ng: list = []
238
239    if msg.get("reactions"):
240        for reactions in msg.get("reactions", {}):
241            if isinstance(reactions, dict) and g.bot_id in reactions.get("users", []):
242                match reactions.get("name"):
243                    case g.cfg.setting.reaction_ok:
244                        reaction_ok.append(msg.get("ts"))
245                    case g.cfg.setting.reaction_ng:
246                        reaction_ng.append(msg.get("ts"))
247
248    return (reaction_ok, reaction_ng)
SlackSearchDict = dict[str, cls.types.SlackSearchData]
DBSearchDict = dict[str, cls.score.GameResult]
def slack_messages(word: str) -> dict[str, cls.types.SlackSearchData]:
21def slack_messages(word: str) -> SlackSearchDict:
22    """slackログからメッセージを検索して返す
23
24    Args:
25        word (str): 検索するワード
26
27    Returns:
28        SlackSearchDict: 検索した結果
29    """
30
31    # 検索クエリ
32    after = ExtDt(days=-g.cfg.search.after).format("ymd", "-")
33    query = f"{word} in:{g.cfg.search.channel} after:{after}"
34    logging.info("query=%s", query)
35
36    # データ取得
37    response = g.webclient.search_messages(
38        query=query,
39        sort="timestamp",
40        sort_dir="asc",
41        count=100
42    )
43    matches = response["messages"]["matches"]  # 1ページ目
44    for p in range(2, response["messages"]["paging"]["pages"] + 1):
45        response = g.webclient.search_messages(
46            query=query,
47            sort="timestamp",
48            sort_dir="asc",
49            count=100,
50            page=p
51        )
52        matches += response["messages"]["matches"]  # 2ページ目以降
53
54    # 必要なデータだけ辞書に格納
55    data: SlackSearchDict = cast(SlackSearchDict, {})
56    for x in matches:
57        if isinstance(x, dict):
58            data[x["ts"]] = {
59                "channel_id": str(cast(dict, x["channel"]).get("id", "")),
60                "user_id": str(x.get("user", "")),
61                "text": str(x.get("text", "")),
62            }
63
64    return data

slackログからメッセージを検索して返す

Arguments:
  • word (str): 検索するワード
Returns:

SlackSearchDict: 検索した結果

def get_message_details( matches: dict[str, cls.types.SlackSearchData]) -> dict[str, cls.types.SlackSearchData]:
 67def get_message_details(matches: SlackSearchDict) -> SlackSearchDict:
 68    """メッセージ詳細情報取得
 69
 70    Args:
 71        matches (SlackSearchDict): 対象データ
 72
 73    Returns:
 74        SlackSearchDict: 詳細情報追加データ
 75    """
 76
 77    # 詳細情報取得
 78    for key, val in matches.items():
 79        res: dict = {}
 80        if isinstance(channel_id := val.get("channel_id"), str):
 81            conversations = g.app.client.conversations_replies(channel=channel_id, ts=key)
 82            if (msg := conversations.get("messages")):
 83                res = cast(dict, msg[0])
 84        else:
 85            continue
 86
 87        if res:
 88            # 各種時間取得
 89            matches[key].update({"event_ts": res.get("ts")})  # イベント発生時間
 90            matches[key].update({"thread_ts": res.get("thread_ts")})  # スレッドの先頭
 91            matches[key].update({"edited_ts": cast(dict, res.get("edited", {})).get("ts")})  # 編集時間
 92            # リアクション取得
 93            reaction_ok, reaction_ng = reactions_list(res)
 94            matches[key].update({"reaction_ok": reaction_ok})
 95            matches[key].update({"reaction_ng": reaction_ng})
 96            # スレッド内フラグ
 97            if val.get("event_ts") == val.get("thread_ts") or val.get("thread_ts") is None:
 98                matches[key].update({"in_thread": False})
 99            else:
100                matches[key].update({"in_thread": True})
101
102    return matches

メッセージ詳細情報取得

Arguments:
  • matches (SlackSearchDict): 対象データ
Returns:

SlackSearchDict: 詳細情報追加データ

def for_slack_score() -> dict[str, cls.types.SlackSearchData]:
105def for_slack_score() -> SlackSearchDict:
106    """過去ログからスコア記録を検索して返す
107
108    Returns:
109        SlackSearchDict: 検索した結果
110    """
111
112    matches = slack_messages(g.cfg.search.keyword)
113
114    # ゲーム結果の抽出
115    for key in list(matches.keys()):
116        if (detection := validator.pattern(matches[key].get("text", ""))):
117            detection.calc(ts=key)
118            if matches[key].get("user_id", "") in g.cfg.setting.ignore_userid:  # 除外ユーザからのポストは破棄
119                logging.info("skip ignore user: %s (%s)", matches[key]["user_id"], detection)
120                matches.pop(key)
121                continue
122            matches[key]["score"] = detection
123            matches[key].pop("text")
124        else:  # 不一致は破棄
125            matches.pop(key)
126
127    # 結果が無い場合は空の辞書を返して後続の処理をスキップ
128    if not matches:
129        return cast(SlackSearchDict, {})
130
131    matches = get_message_details(matches)
132    g.msg.channel_type = "search_messages"
133    return matches

過去ログからスコア記録を検索して返す

Returns:

SlackSearchDict: 検索した結果

def for_slack_remarks() -> dict[str, cls.types.SlackSearchData]:
136def for_slack_remarks() -> SlackSearchDict:
137    """slackログからメモを検索して返す
138
139    Returns:
140        SlackSearchDict: 検索した結果
141    """
142
143    matches = slack_messages(g.cfg.cw.remarks_word)
144
145    # メモの抽出
146    for key in list(matches.keys()):
147        if re.match(rf"^{g.cfg.cw.remarks_word}", matches[key].get("text", "")):  # キーワードが先頭に存在するかチェック
148            text = matches[key]["text"].replace(g.cfg.cw.remarks_word, "").strip().split()
149            if matches[key].get("user_id", "") in g.cfg.setting.ignore_userid:  # 除外ユーザからのポストは破棄
150                logging.info("skip ignore user: %s, (%s)", matches[key]["user_id"], text)
151                matches.pop(key)
152                continue
153            matches[key]["remarks"] = []
154            g.params.update(unregistered_replace=False)  # 名前ブレを修正(ゲスト無効)
155            for name, matter in zip(text[0::2], text[1::2]):
156                matches[key]["remarks"].append((formatter.name_replace(name, False), matter))
157            matches[key].pop("text")
158        else:  # 不一致は破棄
159            matches.pop(key)
160
161    # 結果が無い場合は空の辞書を返して後続の処理をスキップ
162    if not matches:
163        return cast(SlackSearchDict, {})
164
165    matches = get_message_details(matches)
166    g.msg.channel_type = "search_messages"
167    return matches

slackログからメモを検索して返す

Returns:

SlackSearchDict: 検索した結果

def for_db_score(first_ts: float | bool = False) -> dict[str, cls.score.GameResult]:
170def for_db_score(first_ts: float | bool = False) -> DBSearchDict:
171    """データベースからスコアを検索して返す
172
173    Args:
174        first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
175
176    Returns:
177        DBSearchDict: 検索した結果
178    """
179
180    if not first_ts:
181        return {}
182
183    data: DBSearchDict = {}
184    with closing(dbutil.get_connection()) as conn:
185        curs = conn.cursor()
186        rows = curs.execute("select * from result where ts >= ?", (str(first_ts),))
187        for row in rows.fetchall():
188            ts = str(dict(row).get("ts", ""))
189            result = GameResult()
190            result.set(**dict(row))
191            data[ts] = result
192
193    return data

データベースからスコアを検索して返す

Arguments:
  • first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
Returns:

DBSearchDict: 検索した結果

def for_db_remarks(first_ts: float | bool = False) -> list:
196def for_db_remarks(first_ts: float | bool = False) -> list:
197    """データベースからメモを検索して返す
198
199    Args:
200        first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
201
202    Returns:
203        list: 検索した結果
204    """
205
206    if not first_ts:
207        return []
208
209    # データベースからデータ取得
210    data: list = []
211    with closing(dbutil.get_connection()) as cur:
212        # 記録済みメモ内容
213        rows = cur.execute("select * from remarks where thread_ts>=?", (str(first_ts),))
214        for row in rows.fetchall():
215            data.append({
216                "thread_ts": row["thread_ts"],
217                "event_ts": row["event_ts"],
218                "name": row["name"],
219                "matter": row["matter"],
220            })
221
222    return data

データベースからメモを検索して返す

Arguments:
  • first_ts (Union[float, bool], optional): 検索を開始する時刻. Defaults to False.
Returns:

list: 検索した結果

def reactions_list(msg: dict) -> tuple[list, list]:
225def reactions_list(msg: dict) -> tuple[list, list]:
226    """botが付けたリアクションを取得
227
228    Args:
229        msg (dict): メッセージ内容
230
231    Returns:
232        tuple[list, list]:
233        - reaction_ok: okが付いているメッセージのタイムスタンプ
234        - reaction_ng: ngが付いているメッセージのタイムスタンプ
235    """
236
237    reaction_ok: list = []
238    reaction_ng: list = []
239
240    if msg.get("reactions"):
241        for reactions in msg.get("reactions", {}):
242            if isinstance(reactions, dict) and g.bot_id in reactions.get("users", []):
243                match reactions.get("name"):
244                    case g.cfg.setting.reaction_ok:
245                        reaction_ok.append(msg.get("ts"))
246                    case g.cfg.setting.reaction_ng:
247                        reaction_ng.append(msg.get("ts"))
248
249    return (reaction_ok, reaction_ng)

botが付けたリアクションを取得

Arguments:
  • msg (dict): メッセージ内容
Returns:

tuple[list, list]:

  • reaction_ok: okが付いているメッセージのタイムスタンプ
  • reaction_ng: ngが付いているメッセージのタイムスタンプ