-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
237 lines (188 loc) · 9.05 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import asyncio
import logging
from typing import Any, Dict
import settings
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from slack_bolt.async_app import AsyncApp
from src import handlers
from src.clients import slack_client
from src.db_session import setup_db
from src.logger_config import setup_loggers
from src.utils import get_channel_id_from_body
custom_logger = setup_loggers("__app__")
app = AsyncApp(
logger=custom_logger,
token=settings.SLACK_BOT_TOKEN,
signing_secret=settings.SLACK_SIGNING_SECRET,
)
temporary_storage: Dict[str, Any] = {}
"""
ゲームの流れ
1. 管理者が `/invite_players`コマンドを実行して、参加者を招待。
2. 管理者が `/prepare` コマンドを実行して、ゲームの事前情報を参加者に送信。
3. 管理者が `start_game` コマンドを実行して、ゲームを開始。ゲームに関する質疑応答を行う。
- `/start_game 10` のようにすると制限時間を10分に設定し、全体に通知出来る。(以下の`/start_vote`コマンド以外の`/start`コマンドも同様)
4. 管理者が `/start_discussion` コマンドを実行して、議論を開始。
- インタラクティブメッセージとモーダルを通じて、追加情報があるかと、何番目の追加情報を送るかを選択。
(optional) 管理者が `/start_organizing` コマンドを実行して、これまでの整理パートを開始。(整理する時間があるかはシナリオによる)
※ ユーザー毎が個別に議論を行うシナリオには対応していない。
6. 管理者が `/start_vote` コマンドを実行して、モーダルを通じて投票を開始。
- この時、アノテーション用のスプレッドシートに全ての発話が保存される。
7. 参加者が `/vote` コマンドを実行して、投票を行う。
8. 全ての参加者が投票を終えたら、管理者はモーダルを通じて各プレイヤーのポイントと犯人役が勝利したかを記入する。
9. 参加者は、投票後、アノテーション用のスプレッドシートにアノテーションを行い、終わったら、「アノテーション完了ボタン」を押す。
10. 管理者のポイント記入と、参加者のアノテーションが完了したタイミングで、結果が発表される。
"""
@app.command("/invite_players")
async def handle_invite_command(ack: Any, body: Dict[str, Any]) -> None:
"""
Google Spreadsheetから必要情報をDBに保存し、ゲームの参加者をチャンネルに招待する
"""
await ack()
await handlers.handle_invite_command(body)
@app.command("/prepare")
async def handle_prepare_command(ack: Any, body: Dict[str, Any]) -> None:
"""
ゲームの参加者に事前情報を送信する。
(デバッグ用)すでに`vote_done`, `annotation_done`, `score_done`などが
Trueになっている場合は、それらをFalseに初期化する。
"""
await ack()
await handlers.handle_prepare_command(body)
@app.command("/start_game")
@app.command("/start_discussion")
@app.command("/start_organizing")
@app.command("/start_final_discussion")
@app.command("/start_vote")
async def handle_start_any_command(ack: Any, body: Dict[str, Any]) -> None:
"""
ゲームの各フェーズを開始する。
コマンドの後に制限時間を指定可(分)。
`/start_vote` のときは、アノテーション用のスプレッドシート作成も行う。
"""
await ack()
await handlers.handle_start_command(body, temporary_storage)
@app.action("send_additional_info_yes")
async def handle_send_additional_info_yes(ack: Any, body: Dict[str, Any]) -> None:
"""
追加情報がある場合のモーダルで「はい」を押した場合の処理
"""
await ack()
await handlers.handle_send_additional_info_yes(body)
@app.action("send_additional_info_no")
async def handle_send_additional_info_no(ack: Any, body: Dict[str, Any]) -> None:
"""
追加情報がある場合のモーダルで「いいえ」を押した場合の処理
"""
await ack()
await handlers.handle_send_additional_info_no(body, temporary_storage)
@app.view("select_additional_info")
async def handle_select_additional_info_submission(ack: Any, body: Dict[str, Any]) -> None:
"""
送る追加情報を選択したモーダルのcallback
"""
await ack()
await handlers.handle_select_additional_info_submission(body, temporary_storage)
@app.command("/vote")
async def handle_vote_command(ack: Any, body: Dict[str, Any]) -> None:
"""
参加者が投票とその理由を行うためのモーダルを送信する。
※ 投票は一度切り
もし、全員が投票済みの場合は、管理者にスコアリングをするようにメッセージを送る。
"""
await ack()
await handlers.handle_vote_command(body)
@app.view("vote_submission")
async def handle_vote_submission(ack: Any, body: Dict[str, Any]) -> None:
"""
投票結果と投票理由をスプレッドシートとDBに保存し、ユーザーにアノテーションを行うよう「ポイント計算ボタン」を送る。
"""
await ack()
await handlers.handle_vote_submission(body)
@app.action("open_spreadsheet")
async def on_open_spreadsheet(ack: Any, body: Dict[str, Any]) -> None:
"""
アノテーション用のスプレッドシートが開かれたら、「アノテーション完了ボタン」」を送信する。
"""
await ack()
await handlers.on_open_spreadsheet(body)
@app.action("annotation_done")
async def on_annotation_done(ack: Any, body: Dict[str, Any]) -> None:
"""
「「アノテーション完了ボタン」」が押されたら、DBにアノテーション完了と記録する。
もし、全員のアノテーションが完了している and スコアリングが完了している 場合は、結果発表をする。
"""
await ack()
await handlers.on_annotation_done(body)
@app.action("start_scoring")
async def on_start_scoring(ack: Any, body: Dict[str, Any]) -> None:
"""
管理者が「ポイント計算ボタン」を押したら、ポイント記入用モーダルを送信する。
"""
await ack()
await handlers.on_start_scoring(body)
@app.view("score_submission")
async def handle_score_submission(ack: Any, body: Dict[str, Any]) -> None:
"""
管理者の記入したポイントと犯人が勝利したかをDBとスプレッドシートに保存する。
もし、全員のアノテーションが完了している and スコアリングが完了している 場合は、結果発表をする。
"""
await ack()
await handlers.handle_score_submission(body)
# マニュアルコマンド
@app.command("/scoring")
async def handle_scoring_command(ack: Any, body: Dict[str, Any]) -> None:
"""
ポイント入力ボタンの自動送信失敗時に使うコマンド
"""
await ack()
await handlers.on_start_scoring(body)
@app.command("/announce_result")
async def handle_announce_result_command(ack: Any, body: Dict[str, Any]) -> None:
"""
自動での点数発表に失敗した際に使うコマンド
"""
await ack()
await handlers.handle_announce_result_command(body)
# デバッグ用コマンド
@app.command("/reset")
async def handle_reset_command(ack: Any, body: Dict[str, Any]) -> None:
await ack()
await handlers.handle_reset_command(body)
@app.command("/load_master_sheets")
async def handle_load_master_sheets(ack: Any, body: Dict[str, Any]) -> None:
await ack()
await handlers.handle_load_master_sheets_command(body)
@app.command("/save_messages")
async def handle_save_messages(ack: Any, body: Dict[str, Any]) -> None:
await ack()
await handlers.handle_save_messages_command(body)
@app.command("/print_db")
async def handle_print_db(ack: Any, body: Dict[str, Any]) -> None:
await ack()
await handlers.handle_print_db_command(body)
# 全てのアノテーションシートをダウンロード
@app.command("/download")
async def handle_download(ack: Any, body: Dict[str, Any]) -> None:
"""
全てのアノテーションをjsonl形式で保存し、ダウンロードリンクを管理者のDMに送信する。
"""
await ack()
await handlers.handle_download(body)
@app.error
async def custom_error_handler(
error: Exception, body: Dict[str, Any], logger: logging.Logger
) -> None:
channel_id = get_channel_id_from_body(body)
error_message = getattr(error, "message", str(error))
error_type = type(error).__name__
logger.error(f"<#{channel_id}> Error Type: {error_type}, Message: {error_message}")
logger.error("Error Details:", exc_info=True)
async def main() -> None:
app.error(custom_error_handler)
handler = AsyncSocketModeHandler(app=app, app_token=settings.SLACK_APP_TOKEN)
setup_db()
slack_client.send_direct_message(user_id=settings.STAFF_ID, message="Bot is online")
await handler.start_async() # type: ignore
if __name__ == "__main__":
asyncio.run(main())