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:
133def reply(message: str, rpoint_sum=0) -> str:
134    """メッセージをランダムに返す
135
136    Args:
137        message (str): 選択するメッセージ
138        rpoint_sum (int, optional): 素点合計(1/100). Defaults to 0.
139
140    Returns:
141        str: メッセージ
142    """
143
144    correct_score = g.cfg.mahjong.origin_point * 4  # 配給原点
145    rpoint_diff = abs(correct_score - rpoint_sum)
146
147    default_message = {
148        "invalid_argument": "使い方が間違っています。",
149        "no_hits": "{start}{end} に≪{keyword}≫はありません。",
150        "no_target": "集計対象データがありません。",
151        "invalid_score": "素点合計:{rpoint_sum}\n点数差分:{rpoint_diff}",
152        "restricted_channel": "<@{user_id}> この投稿はデータベースに反映されません。",
153        "inside_thread": "<@{user_id}> スレッド内から成績登録はできません。",
154    }
155
156    msg = default_message.get(message, "")
157
158    if cast(ConfigParser, getattr(g.cfg, "_parser")).has_section("custom_message"):
159        msg_list = []
160        for key, val in cast(ConfigParser, getattr(g.cfg, "_parser")).items("custom_message"):
161            if key.startswith(message):
162                msg_list.append(val)
163        if msg_list:
164            msg = random.choice(msg_list)
165
166    try:
167        msg = str(msg.format(
168            user_id=g.msg.user_id,
169            keyword=g.cfg.search.keyword,
170            start=ExtDt(g.params.get("starttime", ExtDt())).format("ymd"),
171            end=ExtDt(g.params.get("onday", ExtDt())).format("ymd"),
172            rpoint_diff=rpoint_diff * 100,
173            rpoint_sum=rpoint_sum * 100,
174        ))
175    except KeyError as e:
176        logging.error("[unknown keywords] %s: %s", e, msg)
177        msg = msg.replace("{user_id}", g.msg.user_id)
178
179    return msg

メッセージをランダムに返す

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: 条件をまとめた文字列

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場合は見出し付き文字列
def item_aggregation_range(game_info: cls.types.GameInfoDict, 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: 称号