libs.functions.message
libs/functions/message.py
1""" 2libs/functions/message.py 3""" 4 5import logging 6import math 7import random 8import re 9import textwrap 10from configparser import ConfigParser 11from typing import cast 12 13import libs.global_value as g 14from cls.timekit import ExtendedDatetime as ExtDt 15from cls.types import GameInfoDict 16from libs.data import aggregate, lookup 17 18 19def slash_help(command): 20 """スラッシュコマンド用ヘルプ 21 22 Args: 23 command (str): スラッシュコマンド名 24 25 Returns: 26 str: ヘルプメッセージ 27 """ 28 29 msg = "```使い方:" 30 msg += f"\n\t{command} help このメッセージ" 31 msg += "\n\t--- 成績管理 ---" 32 msg += f"\n\t{command} results 成績出力" 33 msg += f"\n\t{command} ranking ランキング出力" 34 msg += f"\n\t{command} graph ポイント推移グラフを表示" 35 msg += f"\n\t{command} report レポート表示" 36 msg += "\n\t--- データベース操作 ---" 37 msg += f"\n\t{command} check データ突合" 38 msg += f"\n\t{command} download データベースダウンロード" 39 msg += "\n\t--- メンバー管理 ---" 40 msg += f"\n\t{command} member 登録されているメンバー" 41 msg += f"\n\t{command} add | del メンバーの追加/削除" 42 msg += "\n\t--- チーム管理 ---" 43 msg += f"\n\t{command} team_create <チーム名> チームの新規作成" 44 msg += f"\n\t{command} team_del <チーム名> チームの削除" 45 msg += f"\n\t{command} team_add <チーム名> <メンバー名> チームにメンバーを登録" 46 msg += f"\n\t{command} team_remove <メンバー名> 指定したメンバーを未所属にする" 47 msg += f"\n\t{command} team_list チーム名と所属メンバーを表示" 48 msg += f"\n\t{command} team_clear チームデータをすべて削除" 49 msg += "```" 50 51 return msg 52 53 54def help_message(): 55 """チャンネル内呼び出しキーワード用ヘルプ 56 57 Returns: 58 str: ヘルプメッセージ 59 """ 60 61 msg = textwrap.dedent(f"""\ 62 *成績記録キーワード* 63 \t{g.cfg.search.keyword} 64 65 *機能呼び出し* 66 \t`呼び出しキーワード [検索範囲] [対象メンバー] [オプション]` 67 68 \t*成績サマリ* 69 \t\t呼び出しキーワード:{g.cfg.cw.results} 70 \t\t検索範囲デフォルト:{g.cfg.results.aggregation_range} 71 \t*成績グラフ* 72 \t\t呼び出しキーワード:{g.cfg.cw.graph} 73 \t\t検索範囲デフォルト:{g.cfg.graph.aggregation_range} 74 \t*ランキング* 75 \t\t呼び出しキーワード:{g.cfg.cw.ranking} 76 \t\t検索範囲デフォルト:{g.cfg.ranking.aggregation_range} 77 \t\t規定打数デフォルト:全体ゲーム数 × {g.cfg.ranking.stipulated_rate} + 1 78 \t\t出力制限デフォルト:上位 {g.cfg.ranking.ranked} 名 79 \t*レポート* 80 \t\t呼び出しキーワード:{g.cfg.cw.report} 81 \t\t検索範囲デフォルト:{g.cfg.report.aggregation_range} 82 \t*メンバー一覧* 83 \t\t呼び出しキーワード:{g.cfg.cw.member} 84 \t*チーム一覧* 85 \t\t呼び出しキーワード:{g.cfg.cw.team} 86 """) 87 88 # 検索範囲 89 msg += "\n\n*検索範囲に指定できるキーワード*\n" 90 msg += textwrap.indent(ExtDt.print_range(), "\t") 91 92 # ルール識別子 93 rule = lookup.db.rule_version_range() 94 if rule: 95 msg += "\n\n*ルール識別子*\n" 96 for key, val in rule.items(): 97 msg += f"\t{key}:{val["first_time"]} ~ {val["last_time"]}\n" 98 99 # メモ機能 100 msg += textwrap.dedent(f"""\ 101 *メモ機能* 102 \t`登録キーワード <対象メンバー> <登録ワード>` 103 \t登録キーワード:{g.cfg.cw.remarks_word} 104 """) 105 106 words = lookup.db.regulation_list(1) 107 if words: 108 msg += "\n\t*卓外ポイントワード(個人清算)*\n" 109 for word, ex_point in rule: 110 msg += "\t\t{}:{}pt\n".format( # pylint: disable=consider-using-f-string 111 word, 112 str(f"{ex_point:.1f}").replace("-", "▲"), 113 ) 114 115 words = [word for word, _ in lookup.db.regulation_list(2)] 116 if g.cfg.undefined_word == 2: 117 words += ["未登録ワードのすべてを個別にカウント"] 118 if words: 119 msg += f"\n\t*個別カウントワード*\n\t\t{'、'.join(words)}\n" 120 121 words = [word for word, _ in lookup.db.regulation_list(0)] 122 if g.cfg.undefined_word == 0: 123 words += ["未登録ワードのすべてを役満としてカウント"] 124 if words: 125 msg += f"\n\t*役満カウントワード*\n\t\t{'、'.join(words)}\n" 126 127 msg = re.sub(r"\n\n\n", "\n\n", msg, flags=re.MULTILINE) 128 129 return msg.strip() 130 131 132def reply(message: str, rpoint_sum=0) -> str: 133 """メッセージをランダムに返す 134 135 Args: 136 message (str): 選択するメッセージ 137 rpoint_sum (int, optional): 素点合計(1/100). Defaults to 0. 138 139 Returns: 140 str: メッセージ 141 """ 142 143 correct_score = g.cfg.mahjong.origin_point * 4 # 配給原点 144 rpoint_diff = abs(correct_score - rpoint_sum) 145 146 default_message = { 147 "invalid_argument": "使い方が間違っています。", 148 "no_hits": "{start} ~ {end} に≪{keyword}≫はありません。", 149 "no_target": "集計対象データがありません。", 150 "invalid_score": "素点合計:{rpoint_sum}\n点数差分:{rpoint_diff}", 151 "restricted_channel": "<@{user_id}> この投稿はデータベースに反映されません。", 152 "inside_thread": "<@{user_id}> スレッド内から成績登録はできません。", 153 } 154 155 msg = default_message.get(message, "") 156 157 if cast(ConfigParser, getattr(g.cfg, "_parser")).has_section("custom_message"): 158 msg_list = [] 159 for key, val in cast(ConfigParser, getattr(g.cfg, "_parser")).items("custom_message"): 160 if key.startswith(message): 161 msg_list.append(val) 162 if msg_list: 163 msg = random.choice(msg_list) 164 165 try: 166 msg = str(msg.format( 167 user_id=g.msg.user_id, 168 keyword=g.cfg.search.keyword, 169 start=ExtDt(g.params.get("starttime", ExtDt())).format("ymd"), 170 end=ExtDt(g.params.get("onday", ExtDt())).format("ymd"), 171 rpoint_diff=rpoint_diff * 100, 172 rpoint_sum=rpoint_sum * 100, 173 )) 174 except KeyError as e: 175 logging.error("[unknown keywords] %s: %s", e, msg) 176 msg = msg.replace("{user_id}", g.msg.user_id) 177 178 return msg 179 180 181def remarks(headword=False) -> str | list: 182 """引数で指定された集計方法を注記にまとめる 183 184 Args: 185 headword (bool, optional): 見出しを付ける. Defaults to False. 186 187 Returns: 188 Union[list, str]: 189 - `headword` がない場合はリストで返す 190 - `headword` がある場合は文字列で返す 191 """ 192 193 remark: list = [] 194 195 if g.params.get("individual"): # 個人集計時のみ表示 196 if not g.params.get("unregistered_replace"): 197 remark.append("ゲスト置換なし(" + g.cfg.setting.guest_mark + ":未登録プレイヤー)") 198 if not g.params.get("guest_skip"): 199 remark.append("2ゲスト戦の結果を含む") 200 else: # チーム集計時 201 if g.params.get("friendly_fire"): 202 if g.params.get("game_results") and g.params.get("verbose"): 203 remark.append("チーム同卓時の結果を含む(" + g.cfg.setting.guest_mark + ")") 204 else: 205 remark.append("チーム同卓時の結果を含む") 206 if g.params["stipulated"] >= 2: 207 remark.append(f"規定打数 {g.params["stipulated"]} G以上") 208 if g.params.get("rule_version") != g.cfg.mahjong.rule_version: 209 remark.append(f"集計対象ルール {g.params["rule_version"]}") 210 211 if headword: 212 if remark: 213 return "特記事項:" + "、".join(remark) 214 return "特記事項:なし" 215 216 return remark 217 218 219def search_word(headword=False): 220 """キーワード検索条件を返す 221 222 Args: 223 headword (bool, optional): 見出しを付ける. Defaults to False. 224 225 Returns: 226 str: 条件をまとめた文字列 227 """ 228 229 if g.params.get("search_word"): 230 ret = g.params["search_word"].replace("%", "") 231 # 集約条件 232 if g.params.get("group_length"): 233 ret += f"({g.params["group_length"]}文字集約)" 234 else: 235 ret = "" 236 237 if headword: 238 if ret: 239 return f"検索ワード:{ret}" 240 241 return ret 242 243 244def header(game_info: GameInfoDict, add_text="", indent=1): 245 """見出し生成 246 247 Args: 248 game_info (GameInfoDict): 集計範囲のゲーム情報 249 add_text (str, optional): 追加表示するテキスト. Defaults to "". 250 indent (int, optional): 先頭のタブ数. Defaults to 1. 251 252 Returns: 253 str: 生成した見出し 254 """ 255 256 msg = "" 257 258 # 集計範囲 259 if g.params.get("search_word"): # コメント検索の場合はコメントで表示 260 game_range1 = f"最初のゲーム:{game_info["first_comment"]}\n" 261 game_range1 += f"最後のゲーム:{game_info["last_comment"]}\n" 262 else: 263 game_range1 = f"最初のゲーム:{game_info["first_game"].format("ymdhms")}\n" 264 game_range1 += f"最後のゲーム:{game_info["last_game"].format("ymdhms")}\n" 265 game_range2 = item_aggregation_range(game_info) 266 267 # ゲーム数 268 if game_info["game_count"] == 0: 269 msg += f"{reply(message="no_hits")}" 270 else: 271 match g.params.get("command"): 272 case "results": 273 if g.params.get("target_count"): # 直近指定がない場合は検索範囲を付ける 274 msg += game_range1 275 msg += f"総ゲーム数:{game_info["game_count"]} 回{add_text}\n" 276 else: 277 msg += item_search_range() 278 msg += game_range1 279 msg += f"ゲーム数:{game_info["game_count"]} 回{add_text}\n" 280 case "ranking" | "report": 281 msg += game_range2 282 msg += f"集計ゲーム数:{game_info["game_count"]}\n" 283 case _: 284 msg += game_range2 285 msg += f"総ゲーム数:{game_info["game_count"]} 回\n" 286 287 if remarks(): 288 msg += "特記事項:" + "、".join(remarks()) + "\n" 289 if search_word(): 290 msg += "検索ワード:" + search_word() + "\n" 291 292 return textwrap.indent(msg, "\t" * indent) 293 294 295def del_blank_line(text: str): 296 """空行を取り除く 297 298 Args: 299 text (str): 処理するテキスト 300 301 Returns: 302 str: 処理されたテキスト 303 """ 304 305 new_text = [] 306 for x in text.split("\n"): 307 if x.strip() == "": 308 continue 309 if x.strip() == "\t": 310 continue 311 new_text.append(x) 312 313 return "\n".join(new_text) 314 315 316def item_search_range(kind=None, time_pattern=None): 317 """検索範囲を返す(ヘッダ出力用) 318 319 Args: 320 kind (str, optional): 返値のタイプ. Defaults to None. 321 time_pattern (str, optional): 表示させるフォーマットを選択. Defaults to None. 322 323 Returns: 324 Union[list, str]: 325 - `kind` にlistが指定されている場合はリスト 326 - `kind` にstrが指定されている場合は文字列 327 - `kind` がNone場合は見出し付き文字列 328 """ 329 330 starttime: str 331 endtime: str 332 333 match time_pattern: 334 case "day": 335 starttime = ExtDt(g.params["starttime"]).format("ts") 336 endtime = ExtDt(g.params["endtime"]).format("ts") 337 case "time": 338 starttime = ExtDt(g.params["starttime"]).format("ymdhm") 339 endtime = ExtDt(g.params["endtime"]).format("ymdhm") 340 case _: 341 starttime = ExtDt(g.params["starttime"]).format("ymdhms") 342 endtime = ExtDt(g.params["endtime"]).format("ymdhms") 343 344 match kind: 345 case "list": 346 return ([starttime, endtime]) 347 case "str": 348 return f"{starttime} ~ {endtime}\n" 349 case _: 350 return f"検索範囲:{starttime} ~ {endtime}\n" 351 352 353def item_aggregation_range(game_info: GameInfoDict, kind=None): 354 """集計範囲を返す(ヘッダ出力用) 355 356 Args: 357 game_info (GameInfoDict): 集計範囲のゲーム情報 358 kind (str, optional): 表示させるフォーマットを選択. Defaults to None. 359 360 Returns: 361 Union[list, str]: 362 - `kind` にlistが指定されている場合はリスト 363 - `kind` にstrが指定されている場合は文字列 364 - `kind` がNone場合は見出し付き文字列 365 """ 366 367 if g.params.get("search_word"): # コメント検索の場合はコメントで表示 368 first = game_info["first_comment"] 369 last = game_info["last_comment"] 370 else: 371 first = game_info["first_game"].format("ymdhm") 372 last = game_info["last_game"].format("ymdhm") 373 374 match kind: 375 case "list": 376 return ([first, last]) 377 case "str": 378 return f"{first} ~ {last}\n" 379 case _: 380 return f"集計範囲:{first} ~ {last}\n" 381 382 383def item_date_range(kind: str, prefix_a: str | None = None, prefix_b: str | None = None) -> str: 384 """日付範囲文字列 385 386 Args: 387 kind (str): ExtendedDatetimeのformatメソッドに渡す引数 388 - *_o: 表示にondayを使用 389 prefix_a (str | None, optional): 単独で返った時の接頭辞. Defaults to None. 390 prefix_b (str | None, optional): 範囲で返った時の接頭辞. Defaults to None. 391 392 Returns: 393 str: 生成文字列 394 """ 395 396 ret: str 397 str_st: str 398 str_et: str 399 st = ExtDt(g.params["starttime"]) 400 et = ExtDt(g.params["endtime"]) 401 ot = ExtDt(g.params["onday"]) 402 403 if kind.startswith("j"): 404 kind = kind.replace("j", "") 405 delimiter = "ja" 406 else: 407 delimiter = "slash" 408 409 if kind.endswith("_o"): 410 kind = kind.replace("_o", "") 411 str_st = st.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 412 str_et = ot.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 413 else: 414 str_st = st.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 415 str_et = et.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 416 417 if st.format(cast(ExtDt.FormatType, kind), delimiter="num") == ot.format(cast(ExtDt.FormatType, kind), delimiter="num"): 418 if prefix_a and prefix_b: 419 ret = f"{prefix_a} ({str_st})" 420 else: 421 ret = f"{str_st}" 422 else: 423 if prefix_a and prefix_b: 424 ret = f"{prefix_b} ({str_st} - {str_et})" 425 else: 426 ret = f"{str_st} - {str_et}" 427 428 return ret 429 430 431def badge_degree(game_count: int = 0) -> str: 432 """プレイしたゲーム数に対して表示される称号を返す 433 434 Args: 435 game_count (int, optional): ゲーム数. Defaults to 0. 436 437 Returns: 438 str: 表示する称号 439 """ 440 441 badge: str = "" 442 443 if g.cfg.badge.degree: 444 445 if (degree_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("degree", "badge", fallback="")): 446 degree_badge = degree_list.split(",") 447 else: 448 return "" 449 if (counter_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("degree", "counter", fallback="")): 450 degree_counter = list(map(int, counter_list.split(","))) 451 for idx, val in enumerate(degree_counter): 452 if game_count >= val: 453 badge = degree_badge[idx] 454 455 return badge 456 457 458def badge_status(game_count: int = 0, win: int = 0) -> str: 459 """勝率に対して付く調子バッジを返す 460 461 Args: 462 game_count (int, optional): ゲーム数. Defaults to 0. 463 win (int, optional): 勝ち数. Defaults to 0. 464 465 Returns: 466 str: 表示する称号 467 """ 468 469 badge: str = "" 470 471 if g.cfg.badge.status: 472 if (status_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("status", "badge", fallback="")): 473 status_badge = status_list.split(",") 474 else: 475 return badge 476 477 if (status_step := cast(ConfigParser, getattr(g.cfg, "_parser")).getfloat("status", "step", fallback="")): 478 if not isinstance(status_step, float): 479 return badge 480 if game_count == 0: 481 index = 0 482 else: 483 winper = win / game_count * 100 484 index = 3 485 for i in (1, 2, 3): 486 if winper <= 50 - status_step * i: 487 index = 4 - i 488 if winper >= 50 + status_step * i: 489 index = 2 + i 490 491 badge = status_badge[index] 492 493 return badge 494 495 496def badge_grade(name: str, detail: bool = True) -> str: 497 """段位表示 498 499 Args: 500 name (str): 対象プレイヤー名 501 detail (bool, optional): 昇段ポイントの表示. Defaults to True. 502 503 Returns: 504 str: 称号 505 """ 506 507 if not g.cfg.badge.grade.display: 508 return "" 509 510 # 初期値 511 point: int = 0 # 昇段ポイント 512 grade_level: int = 0 # レベル(段位) 513 514 result_df = lookup.db.get_results_list(name, g.params.get("rule_version", "")) 515 addition_expression = g.cfg.badge.grade.table.get("addition_expression", "0") 516 for _, data in result_df.iterrows(): 517 rank = data["rank"] 518 rpoint = data["rpoint"] 519 addition_point = math.ceil(eval(addition_expression.format(rpoint=rpoint, origin_point=g.cfg.mahjong.origin_point))) # pylint: disable=eval-used 520 point, grade_level = aggregate.grade_promotion_check(grade_level, point + addition_point, rank) 521 522 next_point = g.cfg.badge.grade.table["table"][grade_level]["point"][1] 523 grade_name = g.cfg.badge.grade.table["table"][grade_level]["grade"] 524 point_detail = f" ({point}/{next_point})" if detail else "" 525 526 return f"{grade_name}{point_detail}"
def
slash_help(command):
20def slash_help(command): 21 """スラッシュコマンド用ヘルプ 22 23 Args: 24 command (str): スラッシュコマンド名 25 26 Returns: 27 str: ヘルプメッセージ 28 """ 29 30 msg = "```使い方:" 31 msg += f"\n\t{command} help このメッセージ" 32 msg += "\n\t--- 成績管理 ---" 33 msg += f"\n\t{command} results 成績出力" 34 msg += f"\n\t{command} ranking ランキング出力" 35 msg += f"\n\t{command} graph ポイント推移グラフを表示" 36 msg += f"\n\t{command} report レポート表示" 37 msg += "\n\t--- データベース操作 ---" 38 msg += f"\n\t{command} check データ突合" 39 msg += f"\n\t{command} download データベースダウンロード" 40 msg += "\n\t--- メンバー管理 ---" 41 msg += f"\n\t{command} member 登録されているメンバー" 42 msg += f"\n\t{command} add | del メンバーの追加/削除" 43 msg += "\n\t--- チーム管理 ---" 44 msg += f"\n\t{command} team_create <チーム名> チームの新規作成" 45 msg += f"\n\t{command} team_del <チーム名> チームの削除" 46 msg += f"\n\t{command} team_add <チーム名> <メンバー名> チームにメンバーを登録" 47 msg += f"\n\t{command} team_remove <メンバー名> 指定したメンバーを未所属にする" 48 msg += f"\n\t{command} team_list チーム名と所属メンバーを表示" 49 msg += f"\n\t{command} team_clear チームデータをすべて削除" 50 msg += "```" 51 52 return msg
スラッシュコマンド用ヘルプ
Arguments:
- command (str): スラッシュコマンド名
Returns:
str: ヘルプメッセージ
def
help_message():
55def help_message(): 56 """チャンネル内呼び出しキーワード用ヘルプ 57 58 Returns: 59 str: ヘルプメッセージ 60 """ 61 62 msg = textwrap.dedent(f"""\ 63 *成績記録キーワード* 64 \t{g.cfg.search.keyword} 65 66 *機能呼び出し* 67 \t`呼び出しキーワード [検索範囲] [対象メンバー] [オプション]` 68 69 \t*成績サマリ* 70 \t\t呼び出しキーワード:{g.cfg.cw.results} 71 \t\t検索範囲デフォルト:{g.cfg.results.aggregation_range} 72 \t*成績グラフ* 73 \t\t呼び出しキーワード:{g.cfg.cw.graph} 74 \t\t検索範囲デフォルト:{g.cfg.graph.aggregation_range} 75 \t*ランキング* 76 \t\t呼び出しキーワード:{g.cfg.cw.ranking} 77 \t\t検索範囲デフォルト:{g.cfg.ranking.aggregation_range} 78 \t\t規定打数デフォルト:全体ゲーム数 × {g.cfg.ranking.stipulated_rate} + 1 79 \t\t出力制限デフォルト:上位 {g.cfg.ranking.ranked} 名 80 \t*レポート* 81 \t\t呼び出しキーワード:{g.cfg.cw.report} 82 \t\t検索範囲デフォルト:{g.cfg.report.aggregation_range} 83 \t*メンバー一覧* 84 \t\t呼び出しキーワード:{g.cfg.cw.member} 85 \t*チーム一覧* 86 \t\t呼び出しキーワード:{g.cfg.cw.team} 87 """) 88 89 # 検索範囲 90 msg += "\n\n*検索範囲に指定できるキーワード*\n" 91 msg += textwrap.indent(ExtDt.print_range(), "\t") 92 93 # ルール識別子 94 rule = lookup.db.rule_version_range() 95 if rule: 96 msg += "\n\n*ルール識別子*\n" 97 for key, val in rule.items(): 98 msg += f"\t{key}:{val["first_time"]} ~ {val["last_time"]}\n" 99 100 # メモ機能 101 msg += textwrap.dedent(f"""\ 102 *メモ機能* 103 \t`登録キーワード <対象メンバー> <登録ワード>` 104 \t登録キーワード:{g.cfg.cw.remarks_word} 105 """) 106 107 words = lookup.db.regulation_list(1) 108 if words: 109 msg += "\n\t*卓外ポイントワード(個人清算)*\n" 110 for word, ex_point in rule: 111 msg += "\t\t{}:{}pt\n".format( # pylint: disable=consider-using-f-string 112 word, 113 str(f"{ex_point:.1f}").replace("-", "▲"), 114 ) 115 116 words = [word for word, _ in lookup.db.regulation_list(2)] 117 if g.cfg.undefined_word == 2: 118 words += ["未登録ワードのすべてを個別にカウント"] 119 if words: 120 msg += f"\n\t*個別カウントワード*\n\t\t{'、'.join(words)}\n" 121 122 words = [word for word, _ in lookup.db.regulation_list(0)] 123 if g.cfg.undefined_word == 0: 124 words += ["未登録ワードのすべてを役満としてカウント"] 125 if words: 126 msg += f"\n\t*役満カウントワード*\n\t\t{'、'.join(words)}\n" 127 128 msg = re.sub(r"\n\n\n", "\n\n", msg, flags=re.MULTILINE) 129 130 return msg.strip()
チャンネル内呼び出しキーワード用ヘルプ
Returns:
str: ヘルプメッセージ
def
reply(message: str, rpoint_sum=0) -> str:
メッセージをランダムに返す
Arguments:
- message (str): 選択するメッセージ
- rpoint_sum (int, optional): 素点合計(1/100). Defaults to 0.
Returns:
str: メッセージ
def
remarks(headword=False) -> str | list:
182def remarks(headword=False) -> str | list: 183 """引数で指定された集計方法を注記にまとめる 184 185 Args: 186 headword (bool, optional): 見出しを付ける. Defaults to False. 187 188 Returns: 189 Union[list, str]: 190 - `headword` がない場合はリストで返す 191 - `headword` がある場合は文字列で返す 192 """ 193 194 remark: list = [] 195 196 if g.params.get("individual"): # 個人集計時のみ表示 197 if not g.params.get("unregistered_replace"): 198 remark.append("ゲスト置換なし(" + g.cfg.setting.guest_mark + ":未登録プレイヤー)") 199 if not g.params.get("guest_skip"): 200 remark.append("2ゲスト戦の結果を含む") 201 else: # チーム集計時 202 if g.params.get("friendly_fire"): 203 if g.params.get("game_results") and g.params.get("verbose"): 204 remark.append("チーム同卓時の結果を含む(" + g.cfg.setting.guest_mark + ")") 205 else: 206 remark.append("チーム同卓時の結果を含む") 207 if g.params["stipulated"] >= 2: 208 remark.append(f"規定打数 {g.params["stipulated"]} G以上") 209 if g.params.get("rule_version") != g.cfg.mahjong.rule_version: 210 remark.append(f"集計対象ルール {g.params["rule_version"]}") 211 212 if headword: 213 if remark: 214 return "特記事項:" + "、".join(remark) 215 return "特記事項:なし" 216 217 return remark
引数で指定された集計方法を注記にまとめる
Arguments:
- headword (bool, optional): 見出しを付ける. Defaults to False.
Returns:
Union[list, str]:
headword
がない場合はリストで返すheadword
がある場合は文字列で返す
def
search_word(headword=False):
220def search_word(headword=False): 221 """キーワード検索条件を返す 222 223 Args: 224 headword (bool, optional): 見出しを付ける. Defaults to False. 225 226 Returns: 227 str: 条件をまとめた文字列 228 """ 229 230 if g.params.get("search_word"): 231 ret = g.params["search_word"].replace("%", "") 232 # 集約条件 233 if g.params.get("group_length"): 234 ret += f"({g.params["group_length"]}文字集約)" 235 else: 236 ret = "" 237 238 if headword: 239 if ret: 240 return f"検索ワード:{ret}" 241 242 return ret
キーワード検索条件を返す
Arguments:
- headword (bool, optional): 見出しを付ける. Defaults to False.
Returns:
str: 条件をまとめた文字列
245def header(game_info: GameInfoDict, add_text="", indent=1): 246 """見出し生成 247 248 Args: 249 game_info (GameInfoDict): 集計範囲のゲーム情報 250 add_text (str, optional): 追加表示するテキスト. Defaults to "". 251 indent (int, optional): 先頭のタブ数. Defaults to 1. 252 253 Returns: 254 str: 生成した見出し 255 """ 256 257 msg = "" 258 259 # 集計範囲 260 if g.params.get("search_word"): # コメント検索の場合はコメントで表示 261 game_range1 = f"最初のゲーム:{game_info["first_comment"]}\n" 262 game_range1 += f"最後のゲーム:{game_info["last_comment"]}\n" 263 else: 264 game_range1 = f"最初のゲーム:{game_info["first_game"].format("ymdhms")}\n" 265 game_range1 += f"最後のゲーム:{game_info["last_game"].format("ymdhms")}\n" 266 game_range2 = item_aggregation_range(game_info) 267 268 # ゲーム数 269 if game_info["game_count"] == 0: 270 msg += f"{reply(message="no_hits")}" 271 else: 272 match g.params.get("command"): 273 case "results": 274 if g.params.get("target_count"): # 直近指定がない場合は検索範囲を付ける 275 msg += game_range1 276 msg += f"総ゲーム数:{game_info["game_count"]} 回{add_text}\n" 277 else: 278 msg += item_search_range() 279 msg += game_range1 280 msg += f"ゲーム数:{game_info["game_count"]} 回{add_text}\n" 281 case "ranking" | "report": 282 msg += game_range2 283 msg += f"集計ゲーム数:{game_info["game_count"]}\n" 284 case _: 285 msg += game_range2 286 msg += f"総ゲーム数:{game_info["game_count"]} 回\n" 287 288 if remarks(): 289 msg += "特記事項:" + "、".join(remarks()) + "\n" 290 if search_word(): 291 msg += "検索ワード:" + search_word() + "\n" 292 293 return textwrap.indent(msg, "\t" * indent)
見出し生成
Arguments:
- game_info (GameInfoDict): 集計範囲のゲーム情報
- add_text (str, optional): 追加表示するテキスト. Defaults to "".
- indent (int, optional): 先頭のタブ数. Defaults to 1.
Returns:
str: 生成した見出し
def
del_blank_line(text: str):
296def del_blank_line(text: str): 297 """空行を取り除く 298 299 Args: 300 text (str): 処理するテキスト 301 302 Returns: 303 str: 処理されたテキスト 304 """ 305 306 new_text = [] 307 for x in text.split("\n"): 308 if x.strip() == "": 309 continue 310 if x.strip() == "\t": 311 continue 312 new_text.append(x) 313 314 return "\n".join(new_text)
空行を取り除く
Arguments:
- text (str): 処理するテキスト
Returns:
str: 処理されたテキスト
def
item_search_range(kind=None, time_pattern=None):
317def item_search_range(kind=None, time_pattern=None): 318 """検索範囲を返す(ヘッダ出力用) 319 320 Args: 321 kind (str, optional): 返値のタイプ. Defaults to None. 322 time_pattern (str, optional): 表示させるフォーマットを選択. Defaults to None. 323 324 Returns: 325 Union[list, str]: 326 - `kind` にlistが指定されている場合はリスト 327 - `kind` にstrが指定されている場合は文字列 328 - `kind` がNone場合は見出し付き文字列 329 """ 330 331 starttime: str 332 endtime: str 333 334 match time_pattern: 335 case "day": 336 starttime = ExtDt(g.params["starttime"]).format("ts") 337 endtime = ExtDt(g.params["endtime"]).format("ts") 338 case "time": 339 starttime = ExtDt(g.params["starttime"]).format("ymdhm") 340 endtime = ExtDt(g.params["endtime"]).format("ymdhm") 341 case _: 342 starttime = ExtDt(g.params["starttime"]).format("ymdhms") 343 endtime = ExtDt(g.params["endtime"]).format("ymdhms") 344 345 match kind: 346 case "list": 347 return ([starttime, endtime]) 348 case "str": 349 return f"{starttime} ~ {endtime}\n" 350 case _: 351 return f"検索範囲:{starttime} ~ {endtime}\n"
検索範囲を返す(ヘッダ出力用)
Arguments:
- kind (str, optional): 返値のタイプ. Defaults to None.
- time_pattern (str, optional): 表示させるフォーマットを選択. Defaults to None.
Returns:
Union[list, str]:
kind
にlistが指定されている場合はリストkind
にstrが指定されている場合は文字列kind
がNone場合は見出し付き文字列
354def item_aggregation_range(game_info: GameInfoDict, kind=None): 355 """集計範囲を返す(ヘッダ出力用) 356 357 Args: 358 game_info (GameInfoDict): 集計範囲のゲーム情報 359 kind (str, optional): 表示させるフォーマットを選択. Defaults to None. 360 361 Returns: 362 Union[list, str]: 363 - `kind` にlistが指定されている場合はリスト 364 - `kind` にstrが指定されている場合は文字列 365 - `kind` がNone場合は見出し付き文字列 366 """ 367 368 if g.params.get("search_word"): # コメント検索の場合はコメントで表示 369 first = game_info["first_comment"] 370 last = game_info["last_comment"] 371 else: 372 first = game_info["first_game"].format("ymdhm") 373 last = game_info["last_game"].format("ymdhm") 374 375 match kind: 376 case "list": 377 return ([first, last]) 378 case "str": 379 return f"{first} ~ {last}\n" 380 case _: 381 return f"集計範囲:{first} ~ {last}\n"
集計範囲を返す(ヘッダ出力用)
Arguments:
- game_info (GameInfoDict): 集計範囲のゲーム情報
- kind (str, optional): 表示させるフォーマットを選択. Defaults to None.
Returns:
Union[list, str]:
kind
にlistが指定されている場合はリストkind
にstrが指定されている場合は文字列kind
がNone場合は見出し付き文字列
def
item_date_range( kind: str, prefix_a: str | None = None, prefix_b: str | None = None) -> str:
384def item_date_range(kind: str, prefix_a: str | None = None, prefix_b: str | None = None) -> str: 385 """日付範囲文字列 386 387 Args: 388 kind (str): ExtendedDatetimeのformatメソッドに渡す引数 389 - *_o: 表示にondayを使用 390 prefix_a (str | None, optional): 単独で返った時の接頭辞. Defaults to None. 391 prefix_b (str | None, optional): 範囲で返った時の接頭辞. Defaults to None. 392 393 Returns: 394 str: 生成文字列 395 """ 396 397 ret: str 398 str_st: str 399 str_et: str 400 st = ExtDt(g.params["starttime"]) 401 et = ExtDt(g.params["endtime"]) 402 ot = ExtDt(g.params["onday"]) 403 404 if kind.startswith("j"): 405 kind = kind.replace("j", "") 406 delimiter = "ja" 407 else: 408 delimiter = "slash" 409 410 if kind.endswith("_o"): 411 kind = kind.replace("_o", "") 412 str_st = st.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 413 str_et = ot.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 414 else: 415 str_st = st.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 416 str_et = et.format(cast(ExtDt.FormatType, kind), delimiter=cast(ExtDt.DelimiterStyle, delimiter)) 417 418 if st.format(cast(ExtDt.FormatType, kind), delimiter="num") == ot.format(cast(ExtDt.FormatType, kind), delimiter="num"): 419 if prefix_a and prefix_b: 420 ret = f"{prefix_a} ({str_st})" 421 else: 422 ret = f"{str_st}" 423 else: 424 if prefix_a and prefix_b: 425 ret = f"{prefix_b} ({str_st} - {str_et})" 426 else: 427 ret = f"{str_st} - {str_et}" 428 429 return ret
日付範囲文字列
Arguments:
- kind (str): ExtendedDatetimeのformatメソッドに渡す引数
- *_o: 表示にondayを使用
- prefix_a (str | None, optional): 単独で返った時の接頭辞. Defaults to None.
- prefix_b (str | None, optional): 範囲で返った時の接頭辞. Defaults to None.
Returns:
str: 生成文字列
def
badge_degree(game_count: int = 0) -> str:
432def badge_degree(game_count: int = 0) -> str: 433 """プレイしたゲーム数に対して表示される称号を返す 434 435 Args: 436 game_count (int, optional): ゲーム数. Defaults to 0. 437 438 Returns: 439 str: 表示する称号 440 """ 441 442 badge: str = "" 443 444 if g.cfg.badge.degree: 445 446 if (degree_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("degree", "badge", fallback="")): 447 degree_badge = degree_list.split(",") 448 else: 449 return "" 450 if (counter_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("degree", "counter", fallback="")): 451 degree_counter = list(map(int, counter_list.split(","))) 452 for idx, val in enumerate(degree_counter): 453 if game_count >= val: 454 badge = degree_badge[idx] 455 456 return badge
プレイしたゲーム数に対して表示される称号を返す
Arguments:
- game_count (int, optional): ゲーム数. Defaults to 0.
Returns:
str: 表示する称号
def
badge_status(game_count: int = 0, win: int = 0) -> str:
459def badge_status(game_count: int = 0, win: int = 0) -> str: 460 """勝率に対して付く調子バッジを返す 461 462 Args: 463 game_count (int, optional): ゲーム数. Defaults to 0. 464 win (int, optional): 勝ち数. Defaults to 0. 465 466 Returns: 467 str: 表示する称号 468 """ 469 470 badge: str = "" 471 472 if g.cfg.badge.status: 473 if (status_list := cast(ConfigParser, getattr(g.cfg, "_parser")).get("status", "badge", fallback="")): 474 status_badge = status_list.split(",") 475 else: 476 return badge 477 478 if (status_step := cast(ConfigParser, getattr(g.cfg, "_parser")).getfloat("status", "step", fallback="")): 479 if not isinstance(status_step, float): 480 return badge 481 if game_count == 0: 482 index = 0 483 else: 484 winper = win / game_count * 100 485 index = 3 486 for i in (1, 2, 3): 487 if winper <= 50 - status_step * i: 488 index = 4 - i 489 if winper >= 50 + status_step * i: 490 index = 2 + i 491 492 badge = status_badge[index] 493 494 return badge
勝率に対して付く調子バッジを返す
Arguments:
- game_count (int, optional): ゲーム数. Defaults to 0.
- win (int, optional): 勝ち数. Defaults to 0.
Returns:
str: 表示する称号
def
badge_grade(name: str, detail: bool = True) -> str:
497def badge_grade(name: str, detail: bool = True) -> str: 498 """段位表示 499 500 Args: 501 name (str): 対象プレイヤー名 502 detail (bool, optional): 昇段ポイントの表示. Defaults to True. 503 504 Returns: 505 str: 称号 506 """ 507 508 if not g.cfg.badge.grade.display: 509 return "" 510 511 # 初期値 512 point: int = 0 # 昇段ポイント 513 grade_level: int = 0 # レベル(段位) 514 515 result_df = lookup.db.get_results_list(name, g.params.get("rule_version", "")) 516 addition_expression = g.cfg.badge.grade.table.get("addition_expression", "0") 517 for _, data in result_df.iterrows(): 518 rank = data["rank"] 519 rpoint = data["rpoint"] 520 addition_point = math.ceil(eval(addition_expression.format(rpoint=rpoint, origin_point=g.cfg.mahjong.origin_point))) # pylint: disable=eval-used 521 point, grade_level = aggregate.grade_promotion_check(grade_level, point + addition_point, rank) 522 523 next_point = g.cfg.badge.grade.table["table"][grade_level]["point"][1] 524 grade_name = g.cfg.badge.grade.table["table"][grade_level]["grade"] 525 point_detail = f" ({point}/{next_point})" if detail else "" 526 527 return f"{grade_name}{point_detail}"
段位表示
Arguments:
- name (str): 対象プレイヤー名
- detail (bool, optional): 昇段ポイントの表示. Defaults to True.
Returns:
str: 称号