libs.domain.modify
libs/domain/modify.py
1""" 2libs/domain/modify.py 3""" 4 5import logging 6import os 7import re 8import shutil 9import sqlite3 10from contextlib import closing 11from typing import TYPE_CHECKING, Any, cast 12 13import libs.global_value as g 14from libs.functions import lookup, message 15from libs.types import ActionStatus, MessageStatus, StyleOptions 16from libs.utils import dbutil, formatter 17from libs.utils.timekit import ExtendedDatetime as ExtDt 18 19if TYPE_CHECKING: 20 from integrations.protocols import MessageParserProtocol 21 from libs.domain.score import GameResult 22 from libs.types import RemarkDict 23 24 25def db_insert(detection: "GameResult", m: "MessageParserProtocol") -> int: 26 """ 27 スコアデータをDBに追加する 28 29 Args: 30 detection (GameResult): スコアデータ 31 m (MessageParserProtocol): メッセージデータ 32 33 Returns: 34 int: DB更新レコード数 35 36 """ 37 changes: int = 0 38 39 if m.check_updatable: 40 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 41 try: 42 cur.execute( 43 dbutil.query("RESULT_INSERT"), 44 { 45 "playtime": ExtDt(float(detection.ts)).format(ExtDt.FMT.SQL), 46 "rpoint_sum": detection.rpoint_sum, 47 **detection.to_dict(), 48 }, 49 ) 50 changes = cur.total_changes 51 cur.commit() 52 except sqlite3.IntegrityError as err: 53 logging.error("IntegrityError: %s", err) 54 logging.info("%s", detection.to_text("logging")) 55 _score_check(detection, m) 56 else: 57 logging.warning("A post to a restricted channel was detected.") 58 m.set_message(message.random_reply(m, "restricted_channel"), StyleOptions(key_title=False)) 59 m.post.ts = m.data.event_ts 60 61 g.adapter.functions.post_processing(m) 62 return changes 63 64 65def db_update(detection: "GameResult", m: "MessageParserProtocol") -> int: 66 """ 67 スコアデータを変更する 68 69 Args: 70 detection (GameResult): スコアデータ 71 m (MessageParserProtocol): メッセージデータ 72 73 Returns: 74 int: DB更新レコード数 75 76 """ 77 detection.calc() 78 changes: int = int(0) 79 80 if m.check_updatable: 81 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 82 cur.execute( 83 dbutil.query("RESULT_UPDATE"), 84 { 85 "playtime": ExtDt(float(detection.ts)).format(ExtDt.FMT.SQL), 86 "rpoint_sum": detection.rpoint_sum, 87 **detection.to_dict(), 88 }, 89 ) 90 changes = cur.total_changes 91 cur.commit() 92 logging.info("%s", detection.to_text("logging")) 93 _score_check(detection, m) 94 else: 95 logging.warning("A post to a restricted channel was detected.") 96 m.set_message(message.random_reply(m, "restricted_channel"), StyleOptions(key_title=False)) 97 m.post.ts = m.data.event_ts 98 99 g.adapter.functions.post_processing(m) 100 return changes 101 102 103def db_delete(m: "MessageParserProtocol") -> None: 104 """ 105 スコアデータを削除する 106 107 Args: 108 m (MessageParserProtocol): メッセージデータ 109 110 """ 111 if m.check_updatable: 112 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 113 # ゲーム結果の削除 114 cur.execute(dbutil.query("RESULT_DELETE"), (m.data.event_ts,)) 115 if delete_result := cur.execute("select changes();").fetchone()[0]: 116 m.status.target_ts.append(m.data.event_ts) 117 logging.info("result: ts=%s, count=%s", m.data.event_ts, delete_result) 118 # メモの削除 119 if remark_list := cur.execute("select event_ts from remarks where thread_ts=?", (m.data.event_ts,)).fetchall(): 120 cur.execute(dbutil.query("REMARKS_DELETE_ALL"), (m.data.event_ts,)) 121 if delete_remark := cur.execute("select changes();").fetchone()[0]: 122 m.status.target_ts.extend([str(x.get("event_ts")) for x in list(map(dict, remark_list))]) 123 logging.info("remark: ts=%s, count=%s", m.data.event_ts, delete_remark) 124 cur.commit() 125 m.status.action = ActionStatus.DELETE 126 g.adapter.functions.post_processing(m) 127 else: 128 logging.warning("A post to a restricted channel was detected.") 129 130 131def db_backup() -> str: 132 """ 133 データベースのバックアップ 134 135 Returns: 136 str: 動作結果メッセージ 137 138 """ 139 if not g.cfg.setting.backup_dir: # バックアップ設定がされていない場合は何もしない 140 return "" 141 142 fname = os.path.splitext(g.cfg.setting.database_file)[0] 143 fext = os.path.splitext(g.cfg.setting.database_file)[1] 144 bktime = ExtDt().format(ExtDt.FMT.EXT) 145 bkfname = os.path.join(g.cfg.setting.backup_dir, os.path.basename(f"{fname}_{bktime}{fext}")) 146 147 if not os.path.isdir(g.cfg.setting.backup_dir): # バックアップディレクトリ作成 148 try: 149 os.mkdir(g.cfg.setting.backup_dir) 150 except OSError as err: 151 logging.warning(err, exc_info=True) 152 logging.warning("Database backup directory creation failed !!!") 153 return "\nバックアップ用ディレクトリ作成の作成に失敗しました。" 154 155 # バックアップディレクトリにコピー 156 try: 157 shutil.copyfile(g.cfg.setting.database_file, bkfname) 158 logging.info("database backup: %s", bkfname) 159 return "\nデータベースをバックアップしました。" 160 except OSError as err: 161 logging.warning(err, exc_info=True) 162 logging.warning("Database backup failed !!!") 163 return "\nデータベースのバックアップに失敗しました。" 164 165 166def remarks_append(m: "MessageParserProtocol", remarks: list["RemarkDict"]) -> None: 167 """ 168 メモをDBに記録する 169 170 Args: 171 m (MessageParserProtocol): メッセージデータ 172 remarks (list[RemarkDict]): メモに残す内容 173 174 """ 175 if m.check_updatable: 176 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 177 for para in remarks: 178 # 親スレッドの情報 179 row = cur.execute("select * from result where ts=:thread_ts", para).fetchone() 180 if row: 181 if para["name"] in [v for k, v in dict(row).items() if str(k).endswith("_name")]: 182 cur.execute(dbutil.query("REMARKS_INSERT"), para) 183 m.status.target_ts.append(para["event_ts"]) 184 if not m.data.channel_id: 185 m.data.channel_id = para["source"].replace(f"{g.adapter.interface_type}_", "") 186 logging.info("insert: %s", para) 187 cur.commit() 188 count = cur.execute("select changes();").fetchone()[0] 189 190 # 後処理 191 if count: 192 m.status.action = ActionStatus.CHANGE 193 m.status.reaction = True 194 else: 195 m.status.action = ActionStatus.NOTHING 196 197 g.adapter.functions.post_processing(m) 198 199 200def remarks_delete(m: "MessageParserProtocol") -> None: 201 """ 202 DBからメモを削除する 203 204 Args: 205 m (MessageParserProtocol): メッセージデータ 206 207 """ 208 if m.check_updatable: 209 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 210 cur.execute(dbutil.query("REMARKS_DELETE_ONE"), (m.data.event_ts,)) 211 cur.commit() 212 if count := cur.execute("select changes();").fetchone()[0]: 213 m.status.target_ts.append(m.data.event_ts) 214 logging.info("ts=%s, count=%s", m.data.event_ts, count) 215 # 後処理 216 m.status.action = ActionStatus.DELETE 217 g.adapter.functions.post_processing(m) 218 219 220def remarks_delete_compar(m: "MessageParserProtocol", para: "RemarkDict") -> None: 221 """ 222 DBからメモを削除する(突合) 223 224 Args: 225 m (MessageParserProtocol): メッセージデータ 226 para (RemarkDict): パラメータ 227 228 """ 229 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 230 cur.execute(dbutil.query("REMARKS_DELETE_COMPAR"), para) 231 cur.commit() 232 233 left = cur.execute("select count() from remarks where event_ts=:event_ts;", para).fetchone()[0] 234 logging.info("ts=%s, count=%s", m.data.event_ts, left) 235 236 # 後処理 237 m.status.action = ActionStatus.DELETE 238 m.status.target_ts.append(para["event_ts"]) 239 m.data.channel_id = para["source"].replace(f"{g.adapter.interface_type}_", "") 240 if left > 0: # メモデータが残っているなら 241 m.status.action = ActionStatus.CHANGE 242 g.adapter.functions.post_processing(m) 243 244 245def check_remarks(m: "MessageParserProtocol") -> None: 246 """ 247 メモの内容を拾ってDBに格納する 248 249 Args: 250 m (MessageParserProtocol): メッセージデータ 251 252 """ 253 game_result = lookup.exsist_record(m.data.thread_ts) 254 if game_result.has_valid_data(): # ゲーム結果のスレッドになっているか 255 remarks: list["RemarkDict"] = [] 256 for name, matter in zip(m.argument[0::2], m.argument[1::2]): 257 remark: "RemarkDict" = { 258 "thread_ts": m.data.thread_ts, 259 "event_ts": m.data.event_ts, 260 "name": formatter.name_replace(name, not_replace=True), 261 "matter": matter, 262 "source": m.status.source, 263 } 264 if remark["name"] in game_result.to_list() and remark not in remarks: 265 remarks.append(remark) 266 267 match m.data.status: 268 case MessageStatus.APPEND: 269 remarks_append(m, remarks) 270 case MessageStatus.CHANGED: 271 remarks_delete(m) 272 remarks_append(m, remarks) 273 case MessageStatus.DELETED: 274 remarks_delete(m) 275 case _: 276 pass 277 278 279def reprocessing_remarks(m: "MessageParserProtocol") -> None: 280 """ 281 スレッドの内容を再処理 282 283 Args: 284 m (MessageParserProtocol): メッセージデータ 285 286 """ 287 res = g.adapter.functions.get_conversations(m) 288 msg = res.get("messages") 289 290 if msg: 291 reply_count = int(cast(dict[str, Any], msg[0]).get("reply_count", 0)) 292 m.data.thread_ts = str(cast(dict[str, Any], msg[0]).get("ts")) 293 294 for x in range(1, reply_count + 1): 295 m.data.text = str(cast(dict[str, str], msg[x]).get("text", "")) 296 if m.data.text: 297 m.data.event_ts = str(cast(dict[str, Any], msg[x]).get("ts")) 298 logging.debug( 299 "(%s/%s) thread_ts=%s, event_ts=%s, %s", 300 x, 301 reply_count, 302 m.data.thread_ts, 303 m.data.event_ts, 304 m.data.text, 305 ) 306 if re.match(rf"^{g.cfg.setting.remarks_word}", m.keyword): 307 check_remarks(m) 308 309 310def _score_check(detection: "GameResult", m: "MessageParserProtocol") -> None: 311 """ 312 スコアデータ格納状態を記録する 313 314 Args: 315 detection (GameResult): スコアデータ 316 m (MessageParserProtocol): メッセージデータ 317 318 """ 319 # 結果 320 m.status.action = ActionStatus.CHANGE 321 m.status.target_ts.append(m.data.event_ts) 322 m.status.reaction = True 323 m.status.message = detection.to_text("detail") 324 m.post.message.clear() 325 326 # 素点合計チェック 327 if detection.deposit: 328 m.status.reaction = False 329 m.status.rpoint_sum = detection.rpoint_sum 330 m.set_message(message.random_reply(m, "invalid_score"), StyleOptions(key_title=False)) 331 m.post.ts = m.data.event_ts 332 333 # プレイヤー名重複チェック 334 if len(set(detection.to_list())) != detection.mode: 335 m.status.reaction = False 336 m.set_message(message.random_reply(m, "same_player"), StyleOptions(key_title=False)) 337 m.post.ts = m.data.event_ts
def
db_insert( detection: libs.domain.score.GameResult, m: integrations.protocols.MessageParserProtocol) -> int:
26def db_insert(detection: "GameResult", m: "MessageParserProtocol") -> int: 27 """ 28 スコアデータをDBに追加する 29 30 Args: 31 detection (GameResult): スコアデータ 32 m (MessageParserProtocol): メッセージデータ 33 34 Returns: 35 int: DB更新レコード数 36 37 """ 38 changes: int = 0 39 40 if m.check_updatable: 41 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 42 try: 43 cur.execute( 44 dbutil.query("RESULT_INSERT"), 45 { 46 "playtime": ExtDt(float(detection.ts)).format(ExtDt.FMT.SQL), 47 "rpoint_sum": detection.rpoint_sum, 48 **detection.to_dict(), 49 }, 50 ) 51 changes = cur.total_changes 52 cur.commit() 53 except sqlite3.IntegrityError as err: 54 logging.error("IntegrityError: %s", err) 55 logging.info("%s", detection.to_text("logging")) 56 _score_check(detection, m) 57 else: 58 logging.warning("A post to a restricted channel was detected.") 59 m.set_message(message.random_reply(m, "restricted_channel"), StyleOptions(key_title=False)) 60 m.post.ts = m.data.event_ts 61 62 g.adapter.functions.post_processing(m) 63 return changes
スコアデータをDBに追加する
Arguments:
- detection (GameResult): スコアデータ
- m (MessageParserProtocol): メッセージデータ
Returns:
int: DB更新レコード数
def
db_update( detection: libs.domain.score.GameResult, m: integrations.protocols.MessageParserProtocol) -> int:
66def db_update(detection: "GameResult", m: "MessageParserProtocol") -> int: 67 """ 68 スコアデータを変更する 69 70 Args: 71 detection (GameResult): スコアデータ 72 m (MessageParserProtocol): メッセージデータ 73 74 Returns: 75 int: DB更新レコード数 76 77 """ 78 detection.calc() 79 changes: int = int(0) 80 81 if m.check_updatable: 82 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 83 cur.execute( 84 dbutil.query("RESULT_UPDATE"), 85 { 86 "playtime": ExtDt(float(detection.ts)).format(ExtDt.FMT.SQL), 87 "rpoint_sum": detection.rpoint_sum, 88 **detection.to_dict(), 89 }, 90 ) 91 changes = cur.total_changes 92 cur.commit() 93 logging.info("%s", detection.to_text("logging")) 94 _score_check(detection, m) 95 else: 96 logging.warning("A post to a restricted channel was detected.") 97 m.set_message(message.random_reply(m, "restricted_channel"), StyleOptions(key_title=False)) 98 m.post.ts = m.data.event_ts 99 100 g.adapter.functions.post_processing(m) 101 return changes
スコアデータを変更する
Arguments:
- detection (GameResult): スコアデータ
- m (MessageParserProtocol): メッセージデータ
Returns:
int: DB更新レコード数
104def db_delete(m: "MessageParserProtocol") -> None: 105 """ 106 スコアデータを削除する 107 108 Args: 109 m (MessageParserProtocol): メッセージデータ 110 111 """ 112 if m.check_updatable: 113 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 114 # ゲーム結果の削除 115 cur.execute(dbutil.query("RESULT_DELETE"), (m.data.event_ts,)) 116 if delete_result := cur.execute("select changes();").fetchone()[0]: 117 m.status.target_ts.append(m.data.event_ts) 118 logging.info("result: ts=%s, count=%s", m.data.event_ts, delete_result) 119 # メモの削除 120 if remark_list := cur.execute("select event_ts from remarks where thread_ts=?", (m.data.event_ts,)).fetchall(): 121 cur.execute(dbutil.query("REMARKS_DELETE_ALL"), (m.data.event_ts,)) 122 if delete_remark := cur.execute("select changes();").fetchone()[0]: 123 m.status.target_ts.extend([str(x.get("event_ts")) for x in list(map(dict, remark_list))]) 124 logging.info("remark: ts=%s, count=%s", m.data.event_ts, delete_remark) 125 cur.commit() 126 m.status.action = ActionStatus.DELETE 127 g.adapter.functions.post_processing(m) 128 else: 129 logging.warning("A post to a restricted channel was detected.")
スコアデータを削除する
Arguments:
- m (MessageParserProtocol): メッセージデータ
def
db_backup() -> str:
132def db_backup() -> str: 133 """ 134 データベースのバックアップ 135 136 Returns: 137 str: 動作結果メッセージ 138 139 """ 140 if not g.cfg.setting.backup_dir: # バックアップ設定がされていない場合は何もしない 141 return "" 142 143 fname = os.path.splitext(g.cfg.setting.database_file)[0] 144 fext = os.path.splitext(g.cfg.setting.database_file)[1] 145 bktime = ExtDt().format(ExtDt.FMT.EXT) 146 bkfname = os.path.join(g.cfg.setting.backup_dir, os.path.basename(f"{fname}_{bktime}{fext}")) 147 148 if not os.path.isdir(g.cfg.setting.backup_dir): # バックアップディレクトリ作成 149 try: 150 os.mkdir(g.cfg.setting.backup_dir) 151 except OSError as err: 152 logging.warning(err, exc_info=True) 153 logging.warning("Database backup directory creation failed !!!") 154 return "\nバックアップ用ディレクトリ作成の作成に失敗しました。" 155 156 # バックアップディレクトリにコピー 157 try: 158 shutil.copyfile(g.cfg.setting.database_file, bkfname) 159 logging.info("database backup: %s", bkfname) 160 return "\nデータベースをバックアップしました。" 161 except OSError as err: 162 logging.warning(err, exc_info=True) 163 logging.warning("Database backup failed !!!") 164 return "\nデータベースのバックアップに失敗しました。"
データベースのバックアップ
Returns:
str: 動作結果メッセージ
def
remarks_append( m: integrations.protocols.MessageParserProtocol, remarks: list[libs.types.RemarkDict]) -> None:
167def remarks_append(m: "MessageParserProtocol", remarks: list["RemarkDict"]) -> None: 168 """ 169 メモをDBに記録する 170 171 Args: 172 m (MessageParserProtocol): メッセージデータ 173 remarks (list[RemarkDict]): メモに残す内容 174 175 """ 176 if m.check_updatable: 177 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 178 for para in remarks: 179 # 親スレッドの情報 180 row = cur.execute("select * from result where ts=:thread_ts", para).fetchone() 181 if row: 182 if para["name"] in [v for k, v in dict(row).items() if str(k).endswith("_name")]: 183 cur.execute(dbutil.query("REMARKS_INSERT"), para) 184 m.status.target_ts.append(para["event_ts"]) 185 if not m.data.channel_id: 186 m.data.channel_id = para["source"].replace(f"{g.adapter.interface_type}_", "") 187 logging.info("insert: %s", para) 188 cur.commit() 189 count = cur.execute("select changes();").fetchone()[0] 190 191 # 後処理 192 if count: 193 m.status.action = ActionStatus.CHANGE 194 m.status.reaction = True 195 else: 196 m.status.action = ActionStatus.NOTHING 197 198 g.adapter.functions.post_processing(m)
メモをDBに記録する
Arguments:
- m (MessageParserProtocol): メッセージデータ
- remarks (list[RemarkDict]): メモに残す内容
201def remarks_delete(m: "MessageParserProtocol") -> None: 202 """ 203 DBからメモを削除する 204 205 Args: 206 m (MessageParserProtocol): メッセージデータ 207 208 """ 209 if m.check_updatable: 210 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 211 cur.execute(dbutil.query("REMARKS_DELETE_ONE"), (m.data.event_ts,)) 212 cur.commit() 213 if count := cur.execute("select changes();").fetchone()[0]: 214 m.status.target_ts.append(m.data.event_ts) 215 logging.info("ts=%s, count=%s", m.data.event_ts, count) 216 # 後処理 217 m.status.action = ActionStatus.DELETE 218 g.adapter.functions.post_processing(m)
DBからメモを削除する
Arguments:
- m (MessageParserProtocol): メッセージデータ
def
remarks_delete_compar( m: integrations.protocols.MessageParserProtocol, para: libs.types.RemarkDict) -> None:
221def remarks_delete_compar(m: "MessageParserProtocol", para: "RemarkDict") -> None: 222 """ 223 DBからメモを削除する(突合) 224 225 Args: 226 m (MessageParserProtocol): メッセージデータ 227 para (RemarkDict): パラメータ 228 229 """ 230 with closing(dbutil.connection(g.cfg.setting.database_file)) as cur: 231 cur.execute(dbutil.query("REMARKS_DELETE_COMPAR"), para) 232 cur.commit() 233 234 left = cur.execute("select count() from remarks where event_ts=:event_ts;", para).fetchone()[0] 235 logging.info("ts=%s, count=%s", m.data.event_ts, left) 236 237 # 後処理 238 m.status.action = ActionStatus.DELETE 239 m.status.target_ts.append(para["event_ts"]) 240 m.data.channel_id = para["source"].replace(f"{g.adapter.interface_type}_", "") 241 if left > 0: # メモデータが残っているなら 242 m.status.action = ActionStatus.CHANGE 243 g.adapter.functions.post_processing(m)
DBからメモを削除する(突合)
Arguments:
- m (MessageParserProtocol): メッセージデータ
- para (RemarkDict): パラメータ
246def check_remarks(m: "MessageParserProtocol") -> None: 247 """ 248 メモの内容を拾ってDBに格納する 249 250 Args: 251 m (MessageParserProtocol): メッセージデータ 252 253 """ 254 game_result = lookup.exsist_record(m.data.thread_ts) 255 if game_result.has_valid_data(): # ゲーム結果のスレッドになっているか 256 remarks: list["RemarkDict"] = [] 257 for name, matter in zip(m.argument[0::2], m.argument[1::2]): 258 remark: "RemarkDict" = { 259 "thread_ts": m.data.thread_ts, 260 "event_ts": m.data.event_ts, 261 "name": formatter.name_replace(name, not_replace=True), 262 "matter": matter, 263 "source": m.status.source, 264 } 265 if remark["name"] in game_result.to_list() and remark not in remarks: 266 remarks.append(remark) 267 268 match m.data.status: 269 case MessageStatus.APPEND: 270 remarks_append(m, remarks) 271 case MessageStatus.CHANGED: 272 remarks_delete(m) 273 remarks_append(m, remarks) 274 case MessageStatus.DELETED: 275 remarks_delete(m) 276 case _: 277 pass
メモの内容を拾ってDBに格納する
Arguments:
- m (MessageParserProtocol): メッセージデータ
280def reprocessing_remarks(m: "MessageParserProtocol") -> None: 281 """ 282 スレッドの内容を再処理 283 284 Args: 285 m (MessageParserProtocol): メッセージデータ 286 287 """ 288 res = g.adapter.functions.get_conversations(m) 289 msg = res.get("messages") 290 291 if msg: 292 reply_count = int(cast(dict[str, Any], msg[0]).get("reply_count", 0)) 293 m.data.thread_ts = str(cast(dict[str, Any], msg[0]).get("ts")) 294 295 for x in range(1, reply_count + 1): 296 m.data.text = str(cast(dict[str, str], msg[x]).get("text", "")) 297 if m.data.text: 298 m.data.event_ts = str(cast(dict[str, Any], msg[x]).get("ts")) 299 logging.debug( 300 "(%s/%s) thread_ts=%s, event_ts=%s, %s", 301 x, 302 reply_count, 303 m.data.thread_ts, 304 m.data.event_ts, 305 m.data.text, 306 ) 307 if re.match(rf"^{g.cfg.setting.remarks_word}", m.keyword): 308 check_remarks(m)
スレッドの内容を再処理
Arguments:
- m (MessageParserProtocol): メッセージデータ