libs.utils.validator

libs/utils/validator.py

  1"""
  2libs/utils/validator.py
  3"""
  4
  5import re
  6from typing import TYPE_CHECKING, Any, Literal, Optional
  7
  8import libs.global_value as g
  9from libs.domain.command import CommandParser
 10from libs.utils import formatter, textutil
 11from libs.utils.timekit import ExtendedDatetime as ExtDt
 12
 13if TYPE_CHECKING:
 14    from integrations.protocols import MessageParserProtocol
 15
 16
 17def check_namepattern(name: str, kind: Literal["member", "team"]) -> tuple[bool, str]:
 18    """
 19    登録制限チェック
 20
 21    Args:
 22        name (str): チェックする名前
 23        kind (str): チェック種別
 24            - member
 25            - team
 26
 27    Returns:
 28        tuple[bool, str]: 判定結果
 29        - bool: 制限チェック結果真偽
 30        - str: 制限理由
 31
 32    """
 33
 34    def _pattern_gen(check_list: list[str]) -> list[str]:
 35        ret: list[str] = []
 36        for x in check_list:
 37            ret.append(x)
 38            ret.append(textutil.str_conv(x, textutil.ConversionType.KtoH))  # ひらがな
 39            ret.append(textutil.str_conv(x, textutil.ConversionType.HtoK))  # カタカナ
 40
 41        return list(set(ret))
 42
 43    check_pattern = _pattern_gen([name, formatter.honor_remove(name)])  # 入力パターン
 44    ret_flg: bool = True
 45    ret_msg: str = "OK"
 46
 47    # 同名チェック
 48    check_list = _pattern_gen(g.cfg.member.all_lists)  # メンバーチェック
 49    if ret_flg and any(x in check_list for x in check_pattern):
 50        ret_flg, ret_msg = False, f"「{name}」は存在するメンバーです。"
 51
 52    check_list = _pattern_gen(g.cfg.team.lists)  # チームチェック
 53    if ret_flg and any(x in check_list for x in check_pattern):
 54        ret_flg, ret_msg = False, f"「{name}」は存在するチームです。"
 55
 56    if ret_flg and g.cfg.member.guest_name in check_pattern:  # ゲストチェック
 57        ret_flg, ret_msg = False, "使用できない名前です。"
 58
 59    # 登録規定チェック
 60    if ret_flg and len(name) > int(getattr(g.cfg, kind).character_limit):  # 文字制限
 61        ret_flg, ret_msg = False, "登録可能文字数を超えています。"
 62
 63    if ret_flg and re.search("[\\;:<>(),!@#*?/`\"']", name) or not name.isprintable():  # 禁則記号
 64        ret_flg, ret_msg = False, "使用できない記号が含まれています。"
 65
 66    # 引数と同名になっていないかチェック
 67    if ret_flg and name in ExtDt.valid_keywords():
 68        ret_flg, ret_msg = False, "検索範囲指定に使用される単語では登録できません。"
 69
 70    if ret_flg and CommandParser().is_valid_command(name):
 71        ret_flg, ret_msg = False, "オプションに使用される単語では登録できません。"
 72
 73    # コマンドチェック
 74    if ret_flg and name in g.cfg.word_list(list(g.keyword_dispatcher) + list(g.command_dispatcher)):
 75        ret_flg, ret_msg = False, "コマンドに使用される単語では登録できません。"
 76
 77    return (ret_flg, ret_msg)
 78
 79
 80def check_score(m: "MessageParserProtocol") -> dict[str, Any]:
 81    """
 82    スコアチェック
 83
 84    Args:
 85        m (MessageParserProtocol): メッセージデータ
 86
 87    Returns:
 88        dict[str, Any]: 結果
 89
 90    """
 91    text = m.data.text
 92    ret: dict[str, Any] = {}
 93
 94    # 記号を置換
 95    replace_chr = [
 96        ("\uff0b", "+"),  # 全角プラス符号
 97        ("\u2212", "-"),  # 全角マイナス符号
 98        ("\uff08", "("),  # 全角丸括弧
 99        ("\uff09", ")"),  # 全角丸括弧
100        ("\u2017", "_"),  # DOUBLE LOW LINE(半角)
101        ("\u200b", " "),  # ZERO WIDTH SPACE(ゼロ幅スペース)
102        ("\u200e", " "),  # LEFT-TO-RIGHT MARK(左から右へのマーク)
103        ("\u200f", " "),  # RIGHT-TO-LEFT MARK(右から左へのマーク)
104        ("\u2061", " "),  # FUNCTION APPLICATION(関数の適用)
105        ("\u2800", " "),  # BRAILLE PATTERN BLANK(点字パターンの空白)
106        ("\ufeff", " "),  # ZERO WIDTH NO-BREAK SPACE(ゼロ幅改行なしスペース)
107    ]
108    for z, h in replace_chr:
109        text = text.replace(z, h)
110
111    text = "".join(text.split())  # 改行/空白削除
112
113    for keyword, rule_version in g.cfg.rule.keyword_mapping.items():
114        # パターンマッチング
115        if mode := g.cfg.rule.to_dict(rule_version).get("mode"):
116            pattern1 = re.compile(rf"^({keyword})" + r"([^0-9()+-]+)([0-9+-]+)" * mode + r"$")
117            pattern2 = re.compile(r"^" + r"([^0-9()+-]+)([0-9+-]+)" * mode + rf"({keyword})$")
118            pattern3 = re.compile(rf"^({keyword})\((.+?)\)" + r"([^0-9()+-]+)([0-9+-]+)" * mode + r"$")
119            pattern4 = re.compile(r"^" + r"([^0-9()+-]+)([0-9+-]+)" * mode + rf"({keyword})\((.+?)\)$")
120        else:
121            raise RuntimeError
122
123        position_map: dict[int, dict[str, Any]] = {
124            3: {
125                "position1": {"p1_name": 1, "p1_str": 2, "p2_name": 3, "p2_str": 4, "p3_name": 5, "p3_str": 6, "comment": None},
126                "position2": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "comment": None},
127                "position3": {"p1_name": 2, "p1_str": 3, "p2_name": 4, "p2_str": 5, "p3_name": 6, "p3_str": 7, "comment": 1},
128                "position4": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "comment": 7},
129            },
130            4: {
131                "position1": {"p1_name": 1, "p1_str": 2, "p2_name": 3, "p2_str": 4, "p3_name": 5, "p3_str": 6, "p4_name": 7, "p4_str": 8, "comment": None},
132                "position2": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "p4_name": 6, "p4_str": 7, "comment": None},
133                "position3": {"p1_name": 2, "p1_str": 3, "p2_name": 4, "p2_str": 5, "p3_name": 6, "p3_str": 7, "p4_name": 8, "p4_str": 9, "comment": 1},
134                "position4": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "p4_name": 6, "p4_str": 7, "comment": 9},
135            },
136        }
137
138        # 情報取り出し
139        position: dict[str, Optional[int]]
140        match text:
141            case text if pattern1.findall(text):
142                msg = pattern1.findall(text)[0]
143                position = position_map[mode]["position1"]
144            case text if pattern2.findall(text):
145                msg = pattern2.findall(text)[0]
146                position = position_map[mode]["position2"]
147            case text if pattern3.findall(text):
148                msg = pattern3.findall(text)[0]
149                position = position_map[mode]["position3"]
150            case text if pattern4.findall(text):
151                msg = pattern4.findall(text)[0]
152                position = position_map[mode]["position4"]
153            case _:
154                continue
155
156        for k, p in position.items():
157            if isinstance(p, int):
158                ret.update({str(k): str(msg[p])})
159            else:
160                ret.update({str(k): p})
161
162        ret.update(
163            source=g.cfg.resolve_channel_id(m.status.source),
164            ts=m.data.event_ts,
165            **g.cfg.rule.to_dict(rule_version),
166        )
167        break
168
169    return ret
def check_namepattern(name: str, kind: Literal['member', 'team']) -> tuple[bool, str]:
18def check_namepattern(name: str, kind: Literal["member", "team"]) -> tuple[bool, str]:
19    """
20    登録制限チェック
21
22    Args:
23        name (str): チェックする名前
24        kind (str): チェック種別
25            - member
26            - team
27
28    Returns:
29        tuple[bool, str]: 判定結果
30        - bool: 制限チェック結果真偽
31        - str: 制限理由
32
33    """
34
35    def _pattern_gen(check_list: list[str]) -> list[str]:
36        ret: list[str] = []
37        for x in check_list:
38            ret.append(x)
39            ret.append(textutil.str_conv(x, textutil.ConversionType.KtoH))  # ひらがな
40            ret.append(textutil.str_conv(x, textutil.ConversionType.HtoK))  # カタカナ
41
42        return list(set(ret))
43
44    check_pattern = _pattern_gen([name, formatter.honor_remove(name)])  # 入力パターン
45    ret_flg: bool = True
46    ret_msg: str = "OK"
47
48    # 同名チェック
49    check_list = _pattern_gen(g.cfg.member.all_lists)  # メンバーチェック
50    if ret_flg and any(x in check_list for x in check_pattern):
51        ret_flg, ret_msg = False, f"「{name}」は存在するメンバーです。"
52
53    check_list = _pattern_gen(g.cfg.team.lists)  # チームチェック
54    if ret_flg and any(x in check_list for x in check_pattern):
55        ret_flg, ret_msg = False, f"「{name}」は存在するチームです。"
56
57    if ret_flg and g.cfg.member.guest_name in check_pattern:  # ゲストチェック
58        ret_flg, ret_msg = False, "使用できない名前です。"
59
60    # 登録規定チェック
61    if ret_flg and len(name) > int(getattr(g.cfg, kind).character_limit):  # 文字制限
62        ret_flg, ret_msg = False, "登録可能文字数を超えています。"
63
64    if ret_flg and re.search("[\\;:<>(),!@#*?/`\"']", name) or not name.isprintable():  # 禁則記号
65        ret_flg, ret_msg = False, "使用できない記号が含まれています。"
66
67    # 引数と同名になっていないかチェック
68    if ret_flg and name in ExtDt.valid_keywords():
69        ret_flg, ret_msg = False, "検索範囲指定に使用される単語では登録できません。"
70
71    if ret_flg and CommandParser().is_valid_command(name):
72        ret_flg, ret_msg = False, "オプションに使用される単語では登録できません。"
73
74    # コマンドチェック
75    if ret_flg and name in g.cfg.word_list(list(g.keyword_dispatcher) + list(g.command_dispatcher)):
76        ret_flg, ret_msg = False, "コマンドに使用される単語では登録できません。"
77
78    return (ret_flg, ret_msg)

登録制限チェック

Arguments:
  • name (str): チェックする名前
  • kind (str): チェック種別
    • member
    • team
Returns:

tuple[bool, str]: 判定結果

  • bool: 制限チェック結果真偽
  • str: 制限理由
def check_score(m: integrations.protocols.MessageParserProtocol) -> dict[str, typing.Any]:
 81def check_score(m: "MessageParserProtocol") -> dict[str, Any]:
 82    """
 83    スコアチェック
 84
 85    Args:
 86        m (MessageParserProtocol): メッセージデータ
 87
 88    Returns:
 89        dict[str, Any]: 結果
 90
 91    """
 92    text = m.data.text
 93    ret: dict[str, Any] = {}
 94
 95    # 記号を置換
 96    replace_chr = [
 97        ("\uff0b", "+"),  # 全角プラス符号
 98        ("\u2212", "-"),  # 全角マイナス符号
 99        ("\uff08", "("),  # 全角丸括弧
100        ("\uff09", ")"),  # 全角丸括弧
101        ("\u2017", "_"),  # DOUBLE LOW LINE(半角)
102        ("\u200b", " "),  # ZERO WIDTH SPACE(ゼロ幅スペース)
103        ("\u200e", " "),  # LEFT-TO-RIGHT MARK(左から右へのマーク)
104        ("\u200f", " "),  # RIGHT-TO-LEFT MARK(右から左へのマーク)
105        ("\u2061", " "),  # FUNCTION APPLICATION(関数の適用)
106        ("\u2800", " "),  # BRAILLE PATTERN BLANK(点字パターンの空白)
107        ("\ufeff", " "),  # ZERO WIDTH NO-BREAK SPACE(ゼロ幅改行なしスペース)
108    ]
109    for z, h in replace_chr:
110        text = text.replace(z, h)
111
112    text = "".join(text.split())  # 改行/空白削除
113
114    for keyword, rule_version in g.cfg.rule.keyword_mapping.items():
115        # パターンマッチング
116        if mode := g.cfg.rule.to_dict(rule_version).get("mode"):
117            pattern1 = re.compile(rf"^({keyword})" + r"([^0-9()+-]+)([0-9+-]+)" * mode + r"$")
118            pattern2 = re.compile(r"^" + r"([^0-9()+-]+)([0-9+-]+)" * mode + rf"({keyword})$")
119            pattern3 = re.compile(rf"^({keyword})\((.+?)\)" + r"([^0-9()+-]+)([0-9+-]+)" * mode + r"$")
120            pattern4 = re.compile(r"^" + r"([^0-9()+-]+)([0-9+-]+)" * mode + rf"({keyword})\((.+?)\)$")
121        else:
122            raise RuntimeError
123
124        position_map: dict[int, dict[str, Any]] = {
125            3: {
126                "position1": {"p1_name": 1, "p1_str": 2, "p2_name": 3, "p2_str": 4, "p3_name": 5, "p3_str": 6, "comment": None},
127                "position2": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "comment": None},
128                "position3": {"p1_name": 2, "p1_str": 3, "p2_name": 4, "p2_str": 5, "p3_name": 6, "p3_str": 7, "comment": 1},
129                "position4": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "comment": 7},
130            },
131            4: {
132                "position1": {"p1_name": 1, "p1_str": 2, "p2_name": 3, "p2_str": 4, "p3_name": 5, "p3_str": 6, "p4_name": 7, "p4_str": 8, "comment": None},
133                "position2": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "p4_name": 6, "p4_str": 7, "comment": None},
134                "position3": {"p1_name": 2, "p1_str": 3, "p2_name": 4, "p2_str": 5, "p3_name": 6, "p3_str": 7, "p4_name": 8, "p4_str": 9, "comment": 1},
135                "position4": {"p1_name": 0, "p1_str": 1, "p2_name": 2, "p2_str": 3, "p3_name": 4, "p3_str": 5, "p4_name": 6, "p4_str": 7, "comment": 9},
136            },
137        }
138
139        # 情報取り出し
140        position: dict[str, Optional[int]]
141        match text:
142            case text if pattern1.findall(text):
143                msg = pattern1.findall(text)[0]
144                position = position_map[mode]["position1"]
145            case text if pattern2.findall(text):
146                msg = pattern2.findall(text)[0]
147                position = position_map[mode]["position2"]
148            case text if pattern3.findall(text):
149                msg = pattern3.findall(text)[0]
150                position = position_map[mode]["position3"]
151            case text if pattern4.findall(text):
152                msg = pattern4.findall(text)[0]
153                position = position_map[mode]["position4"]
154            case _:
155                continue
156
157        for k, p in position.items():
158            if isinstance(p, int):
159                ret.update({str(k): str(msg[p])})
160            else:
161                ret.update({str(k): p})
162
163        ret.update(
164            source=g.cfg.resolve_channel_id(m.status.source),
165            ts=m.data.event_ts,
166            **g.cfg.rule.to_dict(rule_version),
167        )
168        break
169
170    return ret

スコアチェック

Arguments:
  • m (MessageParserProtocol): メッセージデータ
Returns:

dict[str, Any]: 結果