integrations.base.interface
integrations/base/interface.py
1""" 2integrations/base/interface.py 3""" 4 5from abc import ABC, abstractmethod 6from configparser import ConfigParser 7from dataclasses import dataclass, field 8from types import NoneType 9from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, Type, TypeVar 10 11import pandas as pd 12 13if TYPE_CHECKING: 14 from pathlib import Path 15 16 from integrations.protocols import MessageParserProtocol, MsgData, PostData, StatusData 17 from libs.types import MessageType, StyleOptions 18 19ConfigT = TypeVar("ConfigT", bound="IntegrationsConfig") 20ApiT = TypeVar("ApiT", bound="APIInterface") 21FunctionsT = TypeVar("FunctionsT", bound="FunctionsInterface") 22ParserT = TypeVar("ParserT", bound="MessageParserInterface") 23 24 25class AdapterInterface(ABC, Generic[ConfigT, ApiT, FunctionsT, ParserT]): 26 """アダプタインターフェース""" 27 28 interface_type: str 29 """サービス識別子""" 30 31 conf: ConfigT 32 """個別設定データクラス""" 33 api: ApiT 34 """インターフェース操作APIインスタンス""" 35 functions: FunctionsT 36 """サービス専用関数インスタンス""" 37 parser: Type[ParserT] 38 """メッセージパーサクラス""" 39 40 41@dataclass 42class IntegrationsConfig(ABC): 43 """個別設定値""" 44 45 _parser: Optional[ConfigParser] = field(default=None) 46 47 # ディスパッチテーブル用 48 _command_dispatcher: dict[str, Any] = field(default_factory=dict) 49 _keyword_dispatcher: dict[str, Any] = field(default_factory=dict) 50 51 # 共通設定 52 main_conf: Optional[ConfigParser] = field(default=None) 53 """設定ファイル""" 54 channel_config: Optional["Path"] = field(default=None) 55 """チャンネル個別設定状況 56 - *Path*: チャンネル個別設定ファイルパス 57 - *None*: 個別設定を利用していない 58 """ 59 60 slash_command: str = field(default="") 61 """スラッシュコマンド名""" 62 badge_degree: bool = field(default=False) 63 """プレイしたゲーム数に対して表示される称号 64 - *True*: 表示する 65 - *False*: 表示しない 66 """ 67 badge_status: bool = field(default=False) 68 """勝率に対して付く調子バッジ 69 - *True*: 表示する 70 - *False*: 表示しない 71 """ 72 badge_grade: bool = field(default=False) 73 """段位表示 74 - *True*: 表示する 75 - *False*: 表示しない 76 """ 77 78 separate: bool = field(default=False) 79 """スコア入力元識別子別集計フラグ 80 - *True*: 識別子別に集計 81 - *False*: すべて集計 82 """ 83 channel_id: Optional[str] = field(default=None) 84 """チャンネルIDを上書きする""" 85 86 plotting_backend: Literal["matplotlib", "plotly"] = field(default="matplotlib") 87 """グラフ描写ライブラリ""" 88 89 @property 90 def command_dispatcher(self) -> dict[str, Any]: 91 """ 92 コマンドディスパッチテーブルを辞書で取得 93 94 Returns: 95 dict[str, Any]: コマンドディスパッチテーブル 96 97 """ 98 return self._command_dispatcher 99 100 @property 101 def keyword_dispatcher(self) -> dict[str, Any]: 102 """ 103 キーワードディスパッチテーブルを辞書で取得 104 105 Returns: 106 dict[str, Any]: キーワードディスパッチテーブル 107 108 """ 109 return self._keyword_dispatcher 110 111 112class FunctionsInterface(ABC): 113 """個別関数インターフェース""" 114 115 @abstractmethod 116 def post_processing(self, m: "MessageParserProtocol") -> None: 117 """ 118 後処理 119 120 Args: 121 m (MessageParserProtocol): メッセージデータ 122 123 """ 124 125 @abstractmethod 126 def get_conversations(self, m: "MessageParserProtocol") -> dict[str, Any]: 127 """ 128 スレッド情報の取得 129 130 Args: 131 m (MessageParserProtocol): メッセージデータ 132 133 Returns: 134 dict: API response 135 136 """ 137 return {} 138 139 140class APIInterface(ABC): 141 """アダプタAPIインターフェース""" 142 143 @abstractmethod 144 def post(self, m: "MessageParserProtocol") -> None: 145 """ 146 メッセージを出力する 147 148 Args: 149 m (MessageParserProtocol): メッセージデータ 150 151 """ 152 153 154class MessageParserDataMixin: 155 """メッセージ解析共通処理""" 156 157 data: "MsgData" 158 post: "PostData" 159 status: "StatusData" 160 161 def reset(self) -> None: 162 """初期化""" 163 self.data.reset() 164 self.post.reset() 165 self.status.reset() 166 167 def set_headline( 168 self, 169 data: "MessageType", 170 options: "StyleOptions", 171 ) -> None: 172 """ 173 ヘッドラインメッセージをセット 174 175 Args: 176 data (MessageType): 内容 177 options (StyleOptions): 表示オプション 178 179 """ 180 # 空データは登録しない 181 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 182 self.post.headline = None 183 else: 184 self.post.headline = (data, options) 185 186 def set_message( 187 self, 188 data: "MessageType", 189 options: "StyleOptions", 190 ) -> None: 191 """ 192 本文メッセージをセット 193 194 Args: 195 data (MessageType): 内容 196 options (StyleOptions): 表示オプション 197 198 """ 199 # 空データは登録しない 200 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 201 return 202 203 self.post.message.append((data, options)) 204 205 206class MessageParserInterface(ABC): 207 """メッセージ解析インターフェース""" 208 209 data: "MsgData" 210 post: "PostData" 211 status: "StatusData" 212 213 @abstractmethod 214 def parser(self, body: Any) -> None: 215 """ 216 メッセージ解析 217 218 Args: 219 body (Any): 解析データ 220 221 """ 222 223 @property 224 @abstractmethod 225 def in_thread(self) -> bool: 226 """ 227 元メッセージへのリプライとなっているか 228 229 Returns: 230 bool: 真偽値 231 - *True*: リプライの形(リプライ/スレッドなど) 232 - *False*: 通常メッセージ 233 234 """ 235 236 @property 237 @abstractmethod 238 def is_bot(self) -> bool: 239 """ 240 botのポストかチェック 241 242 Returns: 243 bool: 真偽値 244 - *True*: botのポスト 245 - *False*: ユーザのポスト 246 247 """ 248 249 @property 250 @abstractmethod 251 def check_updatable(self) -> bool: 252 """ 253 DB操作の許可チェック 254 255 Returns: 256 bool: 真偽値 257 - *True*: 許可 258 - *False*: 禁止 259 260 """ 261 262 @property 263 @abstractmethod 264 def ignore_user(self) -> bool: 265 """ 266 ignore_useridに存在するユーザかチェック 267 268 Returns: 269 bool: 真偽値 270 - *True*: 存在する(操作禁止ユーザ) 271 - *False*: 存在しない 272 273 """ 274 275 @property 276 def is_command(self) -> bool: 277 """ 278 コマンドで実行されているかチェック 279 280 Returns: 281 bool: 真偽値 282 - *True*: コマンド実行 283 - *False*: 非コマンド(キーワード呼び出し) 284 285 """ 286 return self.status.command_flg 287 288 @property 289 def keyword(self) -> str: 290 """ 291 コマンドとして認識している文字列を返す 292 293 Returns: 294 str: コマンド名 295 296 """ 297 if ret := self.data.text.split(): 298 return ret[0] 299 return self.data.text 300 301 @property 302 def argument(self) -> list[str]: 303 """ 304 コマンド引数として認識している文字列をリストで返す 305 306 Returns: 307 list[str]: 引数リスト 308 309 """ 310 if ret := self.data.text.split(): 311 return ret[1:] 312 return ret 313 314 @property 315 def reply_ts(self) -> str: 316 """ 317 リプライ先のタイムスタンプを取得する 318 319 Returns: 320 str: タイムスタンプ 321 322 """ 323 ret_ts: str = "0" 324 325 # tsが指定されていれば最優先 326 if self.post.ts != "undetermined": 327 return self.post.ts 328 329 # スレッドに返すか 330 if self.post.thread and self.in_thread: # スレッド内 331 if self.data.thread_ts != "undetermined": 332 ret_ts = self.data.thread_ts 333 elif self.post.thread and not self.in_thread: # スレッド外 334 ret_ts = self.data.event_ts 335 336 return ret_ts
class
AdapterInterface(abc.ABC, typing.Generic[~ConfigT, ~ApiT, ~FunctionsT, ~ParserT]):
26class AdapterInterface(ABC, Generic[ConfigT, ApiT, FunctionsT, ParserT]): 27 """アダプタインターフェース""" 28 29 interface_type: str 30 """サービス識別子""" 31 32 conf: ConfigT 33 """個別設定データクラス""" 34 api: ApiT 35 """インターフェース操作APIインスタンス""" 36 functions: FunctionsT 37 """サービス専用関数インスタンス""" 38 parser: Type[ParserT] 39 """メッセージパーサクラス"""
アダプタインターフェース
@dataclass
class
IntegrationsConfig42@dataclass 43class IntegrationsConfig(ABC): 44 """個別設定値""" 45 46 _parser: Optional[ConfigParser] = field(default=None) 47 48 # ディスパッチテーブル用 49 _command_dispatcher: dict[str, Any] = field(default_factory=dict) 50 _keyword_dispatcher: dict[str, Any] = field(default_factory=dict) 51 52 # 共通設定 53 main_conf: Optional[ConfigParser] = field(default=None) 54 """設定ファイル""" 55 channel_config: Optional["Path"] = field(default=None) 56 """チャンネル個別設定状況 57 - *Path*: チャンネル個別設定ファイルパス 58 - *None*: 個別設定を利用していない 59 """ 60 61 slash_command: str = field(default="") 62 """スラッシュコマンド名""" 63 badge_degree: bool = field(default=False) 64 """プレイしたゲーム数に対して表示される称号 65 - *True*: 表示する 66 - *False*: 表示しない 67 """ 68 badge_status: bool = field(default=False) 69 """勝率に対して付く調子バッジ 70 - *True*: 表示する 71 - *False*: 表示しない 72 """ 73 badge_grade: bool = field(default=False) 74 """段位表示 75 - *True*: 表示する 76 - *False*: 表示しない 77 """ 78 79 separate: bool = field(default=False) 80 """スコア入力元識別子別集計フラグ 81 - *True*: 識別子別に集計 82 - *False*: すべて集計 83 """ 84 channel_id: Optional[str] = field(default=None) 85 """チャンネルIDを上書きする""" 86 87 plotting_backend: Literal["matplotlib", "plotly"] = field(default="matplotlib") 88 """グラフ描写ライブラリ""" 89 90 @property 91 def command_dispatcher(self) -> dict[str, Any]: 92 """ 93 コマンドディスパッチテーブルを辞書で取得 94 95 Returns: 96 dict[str, Any]: コマンドディスパッチテーブル 97 98 """ 99 return self._command_dispatcher 100 101 @property 102 def keyword_dispatcher(self) -> dict[str, Any]: 103 """ 104 キーワードディスパッチテーブルを辞書で取得 105 106 Returns: 107 dict[str, Any]: キーワードディスパッチテーブル 108 109 """ 110 return self._keyword_dispatcher
個別設定値
IntegrationsConfig( _parser: configparser.ConfigParser | None = None, _command_dispatcher: dict[str, typing.Any] = <factory>, _keyword_dispatcher: dict[str, typing.Any] = <factory>, main_conf: configparser.ConfigParser | None = None, channel_config: pathlib.Path | None = None, slash_command: str = '', badge_degree: bool = False, badge_status: bool = False, badge_grade: bool = False, separate: bool = False, channel_id: str | None = None, plotting_backend: Literal['matplotlib', 'plotly'] = 'matplotlib')
command_dispatcher: dict[str, typing.Any]
90 @property 91 def command_dispatcher(self) -> dict[str, Any]: 92 """ 93 コマンドディスパッチテーブルを辞書で取得 94 95 Returns: 96 dict[str, Any]: コマンドディスパッチテーブル 97 98 """ 99 return self._command_dispatcher
コマンドディスパッチテーブルを辞書で取得
Returns:
dict[str, Any]: コマンドディスパッチテーブル
keyword_dispatcher: dict[str, typing.Any]
101 @property 102 def keyword_dispatcher(self) -> dict[str, Any]: 103 """ 104 キーワードディスパッチテーブルを辞書で取得 105 106 Returns: 107 dict[str, Any]: キーワードディスパッチテーブル 108 109 """ 110 return self._keyword_dispatcher
キーワードディスパッチテーブルを辞書で取得
Returns:
dict[str, Any]: キーワードディスパッチテーブル
class
FunctionsInterface(abc.ABC):
113class FunctionsInterface(ABC): 114 """個別関数インターフェース""" 115 116 @abstractmethod 117 def post_processing(self, m: "MessageParserProtocol") -> None: 118 """ 119 後処理 120 121 Args: 122 m (MessageParserProtocol): メッセージデータ 123 124 """ 125 126 @abstractmethod 127 def get_conversations(self, m: "MessageParserProtocol") -> dict[str, Any]: 128 """ 129 スレッド情報の取得 130 131 Args: 132 m (MessageParserProtocol): メッセージデータ 133 134 Returns: 135 dict: API response 136 137 """ 138 return {}
個別関数インターフェース
116 @abstractmethod 117 def post_processing(self, m: "MessageParserProtocol") -> None: 118 """ 119 後処理 120 121 Args: 122 m (MessageParserProtocol): メッセージデータ 123 124 """
後処理
Arguments:
- m (MessageParserProtocol): メッセージデータ
@abstractmethod
def
get_conversations( self, m: integrations.protocols.MessageParserProtocol) -> dict[str, typing.Any]:
126 @abstractmethod 127 def get_conversations(self, m: "MessageParserProtocol") -> dict[str, Any]: 128 """ 129 スレッド情報の取得 130 131 Args: 132 m (MessageParserProtocol): メッセージデータ 133 134 Returns: 135 dict: API response 136 137 """ 138 return {}
スレッド情報の取得
Arguments:
- m (MessageParserProtocol): メッセージデータ
Returns:
dict: API response
class
APIInterface(abc.ABC):
141class APIInterface(ABC): 142 """アダプタAPIインターフェース""" 143 144 @abstractmethod 145 def post(self, m: "MessageParserProtocol") -> None: 146 """ 147 メッセージを出力する 148 149 Args: 150 m (MessageParserProtocol): メッセージデータ 151 152 """
アダプタAPIインターフェース
class
MessageParserDataMixin:
155class MessageParserDataMixin: 156 """メッセージ解析共通処理""" 157 158 data: "MsgData" 159 post: "PostData" 160 status: "StatusData" 161 162 def reset(self) -> None: 163 """初期化""" 164 self.data.reset() 165 self.post.reset() 166 self.status.reset() 167 168 def set_headline( 169 self, 170 data: "MessageType", 171 options: "StyleOptions", 172 ) -> None: 173 """ 174 ヘッドラインメッセージをセット 175 176 Args: 177 data (MessageType): 内容 178 options (StyleOptions): 表示オプション 179 180 """ 181 # 空データは登録しない 182 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 183 self.post.headline = None 184 else: 185 self.post.headline = (data, options) 186 187 def set_message( 188 self, 189 data: "MessageType", 190 options: "StyleOptions", 191 ) -> None: 192 """ 193 本文メッセージをセット 194 195 Args: 196 data (MessageType): 内容 197 options (StyleOptions): 表示オプション 198 199 """ 200 # 空データは登録しない 201 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 202 return 203 204 self.post.message.append((data, options))
メッセージ解析共通処理
def
reset(self) -> None:
162 def reset(self) -> None: 163 """初期化""" 164 self.data.reset() 165 self.post.reset() 166 self.status.reset()
初期化
def
set_headline( self, data: None | str | pathlib.Path | pandas.DataFrame, options: libs.types.StyleOptions) -> None:
168 def set_headline( 169 self, 170 data: "MessageType", 171 options: "StyleOptions", 172 ) -> None: 173 """ 174 ヘッドラインメッセージをセット 175 176 Args: 177 data (MessageType): 内容 178 options (StyleOptions): 表示オプション 179 180 """ 181 # 空データは登録しない 182 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 183 self.post.headline = None 184 else: 185 self.post.headline = (data, options)
ヘッドラインメッセージをセット
Arguments:
- data (MessageType): 内容
- options (StyleOptions): 表示オプション
def
set_message( self, data: None | str | pathlib.Path | pandas.DataFrame, options: libs.types.StyleOptions) -> None:
187 def set_message( 188 self, 189 data: "MessageType", 190 options: "StyleOptions", 191 ) -> None: 192 """ 193 本文メッセージをセット 194 195 Args: 196 data (MessageType): 内容 197 options (StyleOptions): 表示オプション 198 199 """ 200 # 空データは登録しない 201 if isinstance(data, NoneType) or (isinstance(data, pd.DataFrame) and data.empty): 202 return 203 204 self.post.message.append((data, options))
本文メッセージをセット
Arguments:
- data (MessageType): 内容
- options (StyleOptions): 表示オプション
class
MessageParserInterface(abc.ABC):
207class MessageParserInterface(ABC): 208 """メッセージ解析インターフェース""" 209 210 data: "MsgData" 211 post: "PostData" 212 status: "StatusData" 213 214 @abstractmethod 215 def parser(self, body: Any) -> None: 216 """ 217 メッセージ解析 218 219 Args: 220 body (Any): 解析データ 221 222 """ 223 224 @property 225 @abstractmethod 226 def in_thread(self) -> bool: 227 """ 228 元メッセージへのリプライとなっているか 229 230 Returns: 231 bool: 真偽値 232 - *True*: リプライの形(リプライ/スレッドなど) 233 - *False*: 通常メッセージ 234 235 """ 236 237 @property 238 @abstractmethod 239 def is_bot(self) -> bool: 240 """ 241 botのポストかチェック 242 243 Returns: 244 bool: 真偽値 245 - *True*: botのポスト 246 - *False*: ユーザのポスト 247 248 """ 249 250 @property 251 @abstractmethod 252 def check_updatable(self) -> bool: 253 """ 254 DB操作の許可チェック 255 256 Returns: 257 bool: 真偽値 258 - *True*: 許可 259 - *False*: 禁止 260 261 """ 262 263 @property 264 @abstractmethod 265 def ignore_user(self) -> bool: 266 """ 267 ignore_useridに存在するユーザかチェック 268 269 Returns: 270 bool: 真偽値 271 - *True*: 存在する(操作禁止ユーザ) 272 - *False*: 存在しない 273 274 """ 275 276 @property 277 def is_command(self) -> bool: 278 """ 279 コマンドで実行されているかチェック 280 281 Returns: 282 bool: 真偽値 283 - *True*: コマンド実行 284 - *False*: 非コマンド(キーワード呼び出し) 285 286 """ 287 return self.status.command_flg 288 289 @property 290 def keyword(self) -> str: 291 """ 292 コマンドとして認識している文字列を返す 293 294 Returns: 295 str: コマンド名 296 297 """ 298 if ret := self.data.text.split(): 299 return ret[0] 300 return self.data.text 301 302 @property 303 def argument(self) -> list[str]: 304 """ 305 コマンド引数として認識している文字列をリストで返す 306 307 Returns: 308 list[str]: 引数リスト 309 310 """ 311 if ret := self.data.text.split(): 312 return ret[1:] 313 return ret 314 315 @property 316 def reply_ts(self) -> str: 317 """ 318 リプライ先のタイムスタンプを取得する 319 320 Returns: 321 str: タイムスタンプ 322 323 """ 324 ret_ts: str = "0" 325 326 # tsが指定されていれば最優先 327 if self.post.ts != "undetermined": 328 return self.post.ts 329 330 # スレッドに返すか 331 if self.post.thread and self.in_thread: # スレッド内 332 if self.data.thread_ts != "undetermined": 333 ret_ts = self.data.thread_ts 334 elif self.post.thread and not self.in_thread: # スレッド外 335 ret_ts = self.data.event_ts 336 337 return ret_ts
メッセージ解析インターフェース
@abstractmethod
def
parser(self, body: Any) -> None:
214 @abstractmethod 215 def parser(self, body: Any) -> None: 216 """ 217 メッセージ解析 218 219 Args: 220 body (Any): 解析データ 221 222 """
メッセージ解析
Arguments:
- body (Any): 解析データ
in_thread: bool
224 @property 225 @abstractmethod 226 def in_thread(self) -> bool: 227 """ 228 元メッセージへのリプライとなっているか 229 230 Returns: 231 bool: 真偽値 232 - *True*: リプライの形(リプライ/スレッドなど) 233 - *False*: 通常メッセージ 234 235 """
元メッセージへのリプライとなっているか
Returns:
bool: 真偽値
- True: リプライの形(リプライ/スレッドなど)
- False: 通常メッセージ
is_bot: bool
237 @property 238 @abstractmethod 239 def is_bot(self) -> bool: 240 """ 241 botのポストかチェック 242 243 Returns: 244 bool: 真偽値 245 - *True*: botのポスト 246 - *False*: ユーザのポスト 247 248 """
botのポストかチェック
Returns:
bool: 真偽値
- True: botのポスト
- False: ユーザのポスト
check_updatable: bool
250 @property 251 @abstractmethod 252 def check_updatable(self) -> bool: 253 """ 254 DB操作の許可チェック 255 256 Returns: 257 bool: 真偽値 258 - *True*: 許可 259 - *False*: 禁止 260 261 """
DB操作の許可チェック
Returns:
bool: 真偽値
- True: 許可
- False: 禁止
ignore_user: bool
263 @property 264 @abstractmethod 265 def ignore_user(self) -> bool: 266 """ 267 ignore_useridに存在するユーザかチェック 268 269 Returns: 270 bool: 真偽値 271 - *True*: 存在する(操作禁止ユーザ) 272 - *False*: 存在しない 273 274 """
ignore_useridに存在するユーザかチェック
Returns:
bool: 真偽値
- True: 存在する(操作禁止ユーザ)
- False: 存在しない
is_command: bool
276 @property 277 def is_command(self) -> bool: 278 """ 279 コマンドで実行されているかチェック 280 281 Returns: 282 bool: 真偽値 283 - *True*: コマンド実行 284 - *False*: 非コマンド(キーワード呼び出し) 285 286 """ 287 return self.status.command_flg
コマンドで実行されているかチェック
Returns:
bool: 真偽値
- True: コマンド実行
- False: 非コマンド(キーワード呼び出し)
keyword: str
289 @property 290 def keyword(self) -> str: 291 """ 292 コマンドとして認識している文字列を返す 293 294 Returns: 295 str: コマンド名 296 297 """ 298 if ret := self.data.text.split(): 299 return ret[0] 300 return self.data.text
コマンドとして認識している文字列を返す
Returns:
str: コマンド名
argument: list[str]
302 @property 303 def argument(self) -> list[str]: 304 """ 305 コマンド引数として認識している文字列をリストで返す 306 307 Returns: 308 list[str]: 引数リスト 309 310 """ 311 if ret := self.data.text.split(): 312 return ret[1:] 313 return ret
コマンド引数として認識している文字列をリストで返す
Returns:
list[str]: 引数リスト
reply_ts: str
315 @property 316 def reply_ts(self) -> str: 317 """ 318 リプライ先のタイムスタンプを取得する 319 320 Returns: 321 str: タイムスタンプ 322 323 """ 324 ret_ts: str = "0" 325 326 # tsが指定されていれば最優先 327 if self.post.ts != "undetermined": 328 return self.post.ts 329 330 # スレッドに返すか 331 if self.post.thread and self.in_thread: # スレッド内 332 if self.data.thread_ts != "undetermined": 333 ret_ts = self.data.thread_ts 334 elif self.post.thread and not self.in_thread: # スレッド外 335 ret_ts = self.data.event_ts 336 337 return ret_ts
リプライ先のタイムスタンプを取得する
Returns:
str: タイムスタンプ