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: 制限理由
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]: 結果