libs.data.modify

lib/data/modify.py

  1"""
  2lib/data/modify.py
  3"""
  4
  5import logging
  6import os
  7import re
  8import shutil
  9from contextlib import closing
 10
 11import libs.global_value as g
 12from cls.score import GameResult
 13from cls.timekit import ExtendedDatetime as ExtDt
 14from libs.data import lookup
 15from libs.functions import message, slack_api
 16from libs.utils import dbutil, formatter
 17
 18
 19def db_insert(detection: GameResult, reactions_data: list | None = None) -> None:
 20    """スコアデータをDBに追加する
 21
 22    Args:
 23        detection (GameResult): スコアデータ
 24        reactions_data (list | None, optional): リアクションリスト. Defaults to None.
 25    """
 26
 27    detection.calc(ts=g.msg.event_ts)
 28    param = {
 29        "playtime": ExtDt(float(g.msg.event_ts)).format("sql"),
 30        "reactions_data": reactions_data,
 31        "rpoint_sum": detection.rpoint_sum(),
 32        **detection.to_dict(),
 33    }
 34
 35    if g.msg.updatable:
 36        with closing(dbutil.get_connection()) as cur:
 37            cur.execute(g.sql["RESULT_INSERT"], param)
 38            cur.commit()
 39        logging.notice("%s, user=%s", detection, g.msg.user_id)  # type: ignore
 40        score_reactions(param)
 41    else:
 42        slack_api.post_message(message.reply(message="restricted_channel"), g.msg.event_ts)
 43
 44
 45def db_update(detection: GameResult, reactions_data: list | None = None) -> None:
 46    """スコアデータを変更する
 47
 48    Args:
 49        detection (GameResult): スコアデータ
 50        reactions_data (list | None, optional): リアクションリスト. Defaults to None.
 51    """
 52
 53    detection.calc(ts=g.msg.event_ts)
 54    param = {
 55        "playtime": ExtDt(float(g.msg.event_ts)).format("sql"),
 56        "reactions_data": reactions_data,
 57        "rpoint_sum": detection.rpoint_sum(),
 58        **detection.to_dict(),
 59    }
 60
 61    if g.msg.updatable:
 62        with closing(dbutil.get_connection()) as cur:
 63            cur.execute(g.sql["RESULT_UPDATE"], param)
 64            cur.commit()
 65        logging.notice("%s, user=%s", detection, g.msg.user_id)  # type: ignore
 66        score_reactions(param)
 67    else:
 68        slack_api.post_message(message.reply(message="restricted_channel"), g.msg.event_ts)
 69
 70
 71def db_delete(ts: str):
 72    """スコアデータを削除する
 73
 74    Args:
 75        ts (str): 削除対象レコードのタイムスタンプ
 76    """
 77
 78    if g.msg.updatable:
 79        with closing(dbutil.get_connection()) as cur:
 80            delete_list = cur.execute("select event_ts from remarks where thread_ts=?", (ts,)).fetchall()
 81            cur.execute(g.sql["RESULT_DELETE"], (ts,))
 82            delete_result = cur.execute("select changes();").fetchone()[0]
 83            cur.execute(g.sql["REMARKS_DELETE_ALL"], (ts,))
 84            delete_remark = cur.execute("select changes();").fetchone()[0]
 85            cur.commit()
 86
 87        if delete_result:
 88            logging.notice("result: ts=%s, user=%s, count=%s", ts, g.msg.user_id, delete_result)  # type: ignore
 89        if delete_remark:
 90            logging.notice("remark: ts=%s, user=%s, count=%s", ts, g.msg.user_id, delete_remark)  # type: ignore
 91
 92        # リアクションをすべて外す
 93        for icon in lookup.api.reactions_status():
 94            slack_api.call_reactions_remove(icon)
 95        # メモのアイコンを外す
 96        for x in delete_list:
 97            for icon in lookup.api.reactions_status(ts=x):
 98                slack_api.call_reactions_remove(icon, ts=x)
 99
100
101def db_backup() -> str:
102    """データベースのバックアップ
103
104    Returns:
105        str: 動作結果メッセージ
106    """
107
108    if not g.cfg.db.backup_dir:  # バックアップ設定がされていない場合は何もしない
109        return ""
110
111    fname = os.path.splitext(g.cfg.db.database_file)[0]
112    fext = os.path.splitext(g.cfg.db.database_file)[1]
113    bktime = ExtDt().format("ext")
114    bkfname = os.path.join(g.cfg.db.backup_dir, os.path.basename(f"{fname}_{bktime}{fext}"))
115
116    if not os.path.isdir(g.cfg.db.backup_dir):  # バックアップディレクトリ作成
117        try:
118            os.mkdir(g.cfg.db.backup_dir)
119        except OSError as e:
120            logging.error(e, exc_info=True)
121            logging.error("Database backup directory creation failed !!!")
122            return "\nバックアップ用ディレクトリ作成の作成に失敗しました。"
123
124    # バックアップディレクトリにコピー
125    try:
126        shutil.copyfile(g.cfg.db.database_file, bkfname)
127        logging.notice("database backup: %s", bkfname)  # type: ignore
128        return "\nデータベースをバックアップしました。"
129    except OSError as e:
130        logging.error(e, exc_info=True)
131        logging.error("Database backup failed !!!")
132        return "\nデータベースのバックアップに失敗しました。"
133
134
135def remarks_append(remarks: dict | list) -> None:
136    """メモをDBに記録する
137
138    Args:
139        remarks (dict | list): メモに残す内容
140    """
141
142    if isinstance(remarks, dict):
143        remarks = [remarks]
144
145    if g.msg.updatable:
146        with closing(dbutil.get_connection()) as cur:
147            for para in remarks:
148                # 親スレッドの情報
149                row = cur.execute("select * from result where ts=:thread_ts", para).fetchone()
150                if row:
151                    if para["name"] in [v for k, v in dict(row).items() if k.endswith("_name")]:
152                        cur.execute(g.sql["REMARKS_INSERT"], para)
153                        logging.notice("insert: %s, user=%s", para, g.msg.user_id)  # type: ignore
154
155                        if g.cfg.setting.reaction_ok not in lookup.api.reactions_status(ts=para.get("event_ts")):
156                            slack_api.call_reactions_add(g.cfg.setting.reaction_ok, ts=para.get("event_ts"))
157
158            cur.commit()
159
160
161def remarks_delete(ts: str) -> None:
162    """DBからメモを削除する
163
164    Args:
165        ts (str): 削除対象レコードのタイムスタンプ
166    """
167
168    if g.msg.updatable:
169        with closing(dbutil.get_connection()) as cur:
170            cur.execute(g.sql["REMARKS_DELETE_ONE"], (ts,))
171            count = cur.execute("select changes();").fetchone()[0]
172            cur.commit()
173
174        if count:
175            logging.notice("ts=%s, user=%s, count=%s", ts, g.msg.user_id, count)  # type: ignore
176
177        if g.msg.status != "message_deleted":
178            if g.cfg.setting.reaction_ok in lookup.api.reactions_status():
179                slack_api.call_reactions_remove(g.cfg.setting.reaction_ok, ts=ts)
180
181
182def remarks_delete_compar(para: dict) -> None:
183    """DBからメモを削除する(突合)
184
185    Args:
186        para (dict): パラメータ
187    """
188
189    ch: str | None
190
191    with closing(dbutil.get_connection()) as cur:
192        cur.execute(g.sql["REMARKS_DELETE_COMPAR"], para)
193        cur.commit()
194
195        left = cur.execute("select count() from remarks where event_ts=:event_ts;", para).fetchone()[0]
196
197    if g.msg.channel_id:
198        ch = g.msg.channel_id
199    else:
200        ch = lookup.api.get_channel_id()
201
202    icon = lookup.api.reactions_status(ts=para.get("event_ts"))
203    if g.cfg.setting.reaction_ok in icon and left == 0:
204        slack_api.call_reactions_remove(g.cfg.setting.reaction_ok, ch=ch, ts=para.get("event_ts"))
205
206
207def check_remarks() -> None:
208    """メモの内容を拾ってDBに格納する"""
209    game_result = lookup.db.exsist_record(g.msg.thread_ts)
210    if game_result.has_valid_data():  # ゲーム結果のスレッドになっているか
211        g.cfg.results.initialization()
212        g.cfg.results.unregistered_replace = False  # ゲスト無効
213
214        remarks: list = []
215        for name, matter in zip(g.msg.argument[0::2], g.msg.argument[1::2]):
216            remark = {
217                "thread_ts": g.msg.thread_ts,
218                "event_ts": g.msg.event_ts,
219                "name": formatter.name_replace(name),
220                "matter": matter,
221            }
222            if remark["name"] in game_result.to_list() and remark not in remarks:
223                remarks.append(remark)
224
225        match g.msg.status:
226            case "message_append":
227                remarks_append(remarks)
228            case "message_changed":
229                remarks_delete(g.msg.event_ts)
230                remarks_append(remarks)
231            case "message_deleted":
232                remarks_delete(g.msg.event_ts)
233
234
235def reprocessing_remarks():
236    """スレッドの内容を再処理"""
237    res = lookup.api.get_conversations()
238    msg = res.get("messages")
239
240    if msg:
241        reply_count = msg[0].get("reply_count", 0)
242        g.msg.thread_ts = msg[0].get("ts")
243
244        for x in range(1, reply_count + 1):
245            g.msg.event_ts = msg[x].get("ts")
246            text = str(msg[x].get("text", ""))
247            logging.info("(%s/%s) thread_ts=%s, event_ts=%s, %s", x, reply_count, g.msg.thread_ts, g.msg.event_ts, text)
248
249            if text:
250                g.msg.keyword = text.split()[0]
251                g.msg.argument = text.split()[1:]
252
253                if re.match(rf"^{g.cfg.cw.remarks_word}", g.msg.keyword):
254                    check_remarks()
255
256
257def score_reactions(param: dict):
258    """素点合計をチェックしリアクションを付ける
259
260    Args:
261        param (dict): 素点データ
262    """
263
264    correct_score = g.cfg.mahjong.origin_point * 4  # 配給原点
265    rpoint_sum = param["rpoint_sum"]  # 素点合計
266
267    if param["reactions_data"]:
268        icon = param["reactions_data"]
269    else:
270        icon = lookup.api.reactions_status()
271
272    if rpoint_sum == correct_score:
273        if g.cfg.setting.reaction_ng in icon:
274            slack_api.call_reactions_remove(g.cfg.setting.reaction_ng)
275        if g.cfg.setting.reaction_ok not in icon:
276            slack_api.call_reactions_add(g.cfg.setting.reaction_ok)
277    else:
278        if g.cfg.setting.reaction_ok in icon:
279            slack_api.call_reactions_remove(g.cfg.setting.reaction_ok)
280        if g.cfg.setting.reaction_ng not in icon:
281            slack_api.call_reactions_add(g.cfg.setting.reaction_ng)
282
283        slack_api.post_message(
284            message.reply(message="invalid_score", rpoint_sum=rpoint_sum),
285            g.msg.event_ts,
286        )
def db_insert( detection: cls.score.GameResult, reactions_data: list | None = None) -> None:
20def db_insert(detection: GameResult, reactions_data: list | None = None) -> None:
21    """スコアデータをDBに追加する
22
23    Args:
24        detection (GameResult): スコアデータ
25        reactions_data (list | None, optional): リアクションリスト. Defaults to None.
26    """
27
28    detection.calc(ts=g.msg.event_ts)
29    param = {
30        "playtime": ExtDt(float(g.msg.event_ts)).format("sql"),
31        "reactions_data": reactions_data,
32        "rpoint_sum": detection.rpoint_sum(),
33        **detection.to_dict(),
34    }
35
36    if g.msg.updatable:
37        with closing(dbutil.get_connection()) as cur:
38            cur.execute(g.sql["RESULT_INSERT"], param)
39            cur.commit()
40        logging.notice("%s, user=%s", detection, g.msg.user_id)  # type: ignore
41        score_reactions(param)
42    else:
43        slack_api.post_message(message.reply(message="restricted_channel"), g.msg.event_ts)

スコアデータをDBに追加する

Arguments:
  • detection (GameResult): スコアデータ
  • reactions_data (list | None, optional): リアクションリスト. Defaults to None.
def db_update( detection: cls.score.GameResult, reactions_data: list | None = None) -> None:
46def db_update(detection: GameResult, reactions_data: list | None = None) -> None:
47    """スコアデータを変更する
48
49    Args:
50        detection (GameResult): スコアデータ
51        reactions_data (list | None, optional): リアクションリスト. Defaults to None.
52    """
53
54    detection.calc(ts=g.msg.event_ts)
55    param = {
56        "playtime": ExtDt(float(g.msg.event_ts)).format("sql"),
57        "reactions_data": reactions_data,
58        "rpoint_sum": detection.rpoint_sum(),
59        **detection.to_dict(),
60    }
61
62    if g.msg.updatable:
63        with closing(dbutil.get_connection()) as cur:
64            cur.execute(g.sql["RESULT_UPDATE"], param)
65            cur.commit()
66        logging.notice("%s, user=%s", detection, g.msg.user_id)  # type: ignore
67        score_reactions(param)
68    else:
69        slack_api.post_message(message.reply(message="restricted_channel"), g.msg.event_ts)

スコアデータを変更する

Arguments:
  • detection (GameResult): スコアデータ
  • reactions_data (list | None, optional): リアクションリスト. Defaults to None.
def db_delete(ts: str):
72def db_delete(ts: str):
73    """スコアデータを削除する
74
75    Args:
76        ts (str): 削除対象レコードのタイムスタンプ
77    """
78
79    if g.msg.updatable:
80        with closing(dbutil.get_connection()) as cur:
81            delete_list = cur.execute("select event_ts from remarks where thread_ts=?", (ts,)).fetchall()
82            cur.execute(g.sql["RESULT_DELETE"], (ts,))
83            delete_result = cur.execute("select changes();").fetchone()[0]
84            cur.execute(g.sql["REMARKS_DELETE_ALL"], (ts,))
85            delete_remark = cur.execute("select changes();").fetchone()[0]
86            cur.commit()
87
88        if delete_result:
89            logging.notice("result: ts=%s, user=%s, count=%s", ts, g.msg.user_id, delete_result)  # type: ignore
90        if delete_remark:
91            logging.notice("remark: ts=%s, user=%s, count=%s", ts, g.msg.user_id, delete_remark)  # type: ignore
92
93        # リアクションをすべて外す
94        for icon in lookup.api.reactions_status():
95            slack_api.call_reactions_remove(icon)
96        # メモのアイコンを外す
97        for x in delete_list:
98            for icon in lookup.api.reactions_status(ts=x):
99                slack_api.call_reactions_remove(icon, ts=x)

スコアデータを削除する

Arguments:
  • ts (str): 削除対象レコードのタイムスタンプ
def db_backup() -> str:
102def db_backup() -> str:
103    """データベースのバックアップ
104
105    Returns:
106        str: 動作結果メッセージ
107    """
108
109    if not g.cfg.db.backup_dir:  # バックアップ設定がされていない場合は何もしない
110        return ""
111
112    fname = os.path.splitext(g.cfg.db.database_file)[0]
113    fext = os.path.splitext(g.cfg.db.database_file)[1]
114    bktime = ExtDt().format("ext")
115    bkfname = os.path.join(g.cfg.db.backup_dir, os.path.basename(f"{fname}_{bktime}{fext}"))
116
117    if not os.path.isdir(g.cfg.db.backup_dir):  # バックアップディレクトリ作成
118        try:
119            os.mkdir(g.cfg.db.backup_dir)
120        except OSError as e:
121            logging.error(e, exc_info=True)
122            logging.error("Database backup directory creation failed !!!")
123            return "\nバックアップ用ディレクトリ作成の作成に失敗しました。"
124
125    # バックアップディレクトリにコピー
126    try:
127        shutil.copyfile(g.cfg.db.database_file, bkfname)
128        logging.notice("database backup: %s", bkfname)  # type: ignore
129        return "\nデータベースをバックアップしました。"
130    except OSError as e:
131        logging.error(e, exc_info=True)
132        logging.error("Database backup failed !!!")
133        return "\nデータベースのバックアップに失敗しました。"

データベースのバックアップ

Returns:

str: 動作結果メッセージ

def remarks_append(remarks: dict | list) -> None:
136def remarks_append(remarks: dict | list) -> None:
137    """メモをDBに記録する
138
139    Args:
140        remarks (dict | list): メモに残す内容
141    """
142
143    if isinstance(remarks, dict):
144        remarks = [remarks]
145
146    if g.msg.updatable:
147        with closing(dbutil.get_connection()) as cur:
148            for para in remarks:
149                # 親スレッドの情報
150                row = cur.execute("select * from result where ts=:thread_ts", para).fetchone()
151                if row:
152                    if para["name"] in [v for k, v in dict(row).items() if k.endswith("_name")]:
153                        cur.execute(g.sql["REMARKS_INSERT"], para)
154                        logging.notice("insert: %s, user=%s", para, g.msg.user_id)  # type: ignore
155
156                        if g.cfg.setting.reaction_ok not in lookup.api.reactions_status(ts=para.get("event_ts")):
157                            slack_api.call_reactions_add(g.cfg.setting.reaction_ok, ts=para.get("event_ts"))
158
159            cur.commit()

メモをDBに記録する

Arguments:
  • remarks (dict | list): メモに残す内容
def remarks_delete(ts: str) -> None:
162def remarks_delete(ts: str) -> None:
163    """DBからメモを削除する
164
165    Args:
166        ts (str): 削除対象レコードのタイムスタンプ
167    """
168
169    if g.msg.updatable:
170        with closing(dbutil.get_connection()) as cur:
171            cur.execute(g.sql["REMARKS_DELETE_ONE"], (ts,))
172            count = cur.execute("select changes();").fetchone()[0]
173            cur.commit()
174
175        if count:
176            logging.notice("ts=%s, user=%s, count=%s", ts, g.msg.user_id, count)  # type: ignore
177
178        if g.msg.status != "message_deleted":
179            if g.cfg.setting.reaction_ok in lookup.api.reactions_status():
180                slack_api.call_reactions_remove(g.cfg.setting.reaction_ok, ts=ts)

DBからメモを削除する

Arguments:
  • ts (str): 削除対象レコードのタイムスタンプ
def remarks_delete_compar(para: dict) -> None:
183def remarks_delete_compar(para: dict) -> None:
184    """DBからメモを削除する(突合)
185
186    Args:
187        para (dict): パラメータ
188    """
189
190    ch: str | None
191
192    with closing(dbutil.get_connection()) as cur:
193        cur.execute(g.sql["REMARKS_DELETE_COMPAR"], para)
194        cur.commit()
195
196        left = cur.execute("select count() from remarks where event_ts=:event_ts;", para).fetchone()[0]
197
198    if g.msg.channel_id:
199        ch = g.msg.channel_id
200    else:
201        ch = lookup.api.get_channel_id()
202
203    icon = lookup.api.reactions_status(ts=para.get("event_ts"))
204    if g.cfg.setting.reaction_ok in icon and left == 0:
205        slack_api.call_reactions_remove(g.cfg.setting.reaction_ok, ch=ch, ts=para.get("event_ts"))

DBからメモを削除する(突合)

Arguments:
  • para (dict): パラメータ
def check_remarks() -> None:
208def check_remarks() -> None:
209    """メモの内容を拾ってDBに格納する"""
210    game_result = lookup.db.exsist_record(g.msg.thread_ts)
211    if game_result.has_valid_data():  # ゲーム結果のスレッドになっているか
212        g.cfg.results.initialization()
213        g.cfg.results.unregistered_replace = False  # ゲスト無効
214
215        remarks: list = []
216        for name, matter in zip(g.msg.argument[0::2], g.msg.argument[1::2]):
217            remark = {
218                "thread_ts": g.msg.thread_ts,
219                "event_ts": g.msg.event_ts,
220                "name": formatter.name_replace(name),
221                "matter": matter,
222            }
223            if remark["name"] in game_result.to_list() and remark not in remarks:
224                remarks.append(remark)
225
226        match g.msg.status:
227            case "message_append":
228                remarks_append(remarks)
229            case "message_changed":
230                remarks_delete(g.msg.event_ts)
231                remarks_append(remarks)
232            case "message_deleted":
233                remarks_delete(g.msg.event_ts)

メモの内容を拾ってDBに格納する

def reprocessing_remarks():
236def reprocessing_remarks():
237    """スレッドの内容を再処理"""
238    res = lookup.api.get_conversations()
239    msg = res.get("messages")
240
241    if msg:
242        reply_count = msg[0].get("reply_count", 0)
243        g.msg.thread_ts = msg[0].get("ts")
244
245        for x in range(1, reply_count + 1):
246            g.msg.event_ts = msg[x].get("ts")
247            text = str(msg[x].get("text", ""))
248            logging.info("(%s/%s) thread_ts=%s, event_ts=%s, %s", x, reply_count, g.msg.thread_ts, g.msg.event_ts, text)
249
250            if text:
251                g.msg.keyword = text.split()[0]
252                g.msg.argument = text.split()[1:]
253
254                if re.match(rf"^{g.cfg.cw.remarks_word}", g.msg.keyword):
255                    check_remarks()

スレッドの内容を再処理

def score_reactions(param: dict):
258def score_reactions(param: dict):
259    """素点合計をチェックしリアクションを付ける
260
261    Args:
262        param (dict): 素点データ
263    """
264
265    correct_score = g.cfg.mahjong.origin_point * 4  # 配給原点
266    rpoint_sum = param["rpoint_sum"]  # 素点合計
267
268    if param["reactions_data"]:
269        icon = param["reactions_data"]
270    else:
271        icon = lookup.api.reactions_status()
272
273    if rpoint_sum == correct_score:
274        if g.cfg.setting.reaction_ng in icon:
275            slack_api.call_reactions_remove(g.cfg.setting.reaction_ng)
276        if g.cfg.setting.reaction_ok not in icon:
277            slack_api.call_reactions_add(g.cfg.setting.reaction_ok)
278    else:
279        if g.cfg.setting.reaction_ok in icon:
280            slack_api.call_reactions_remove(g.cfg.setting.reaction_ok)
281        if g.cfg.setting.reaction_ng not in icon:
282            slack_api.call_reactions_add(g.cfg.setting.reaction_ng)
283
284        slack_api.post_message(
285            message.reply(message="invalid_score", rpoint_sum=rpoint_sum),
286            g.msg.event_ts,
287        )

素点合計をチェックしリアクションを付ける

Arguments:
  • param (dict): 素点データ