medical-kiban commited on
Commit
d6d1925
ยท
1 Parent(s): 00ed5dc

no message

Browse files
Files changed (2) hide show
  1. app/app.py +236 -1
  2. app/calendar_tool.py +0 -240
app/app.py CHANGED
@@ -6,7 +6,19 @@ from dotenv import load_dotenv
6
  import requests
7
  from typing import Optional
8
  from fastapi import FastAPI, Request, Header, HTTPException
9
- import sys
 
 
 
 
 
 
 
 
 
 
 
 
10
  # sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
11
  from calendar_tool import calendar_main
12
  # .env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.
@@ -18,6 +30,229 @@ CHATWORK_WEBHOOK_TOKEN = os.getenv("CHATWORK_WEBHOOK_TOKEN")
18
  # โš ๏ธ 1๋‹จ๊ณ„์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ 'API ํ† ํฐ'
19
  CHATWORK_API_TOKEN = os.getenv("CHATWORK_API_TOKEN")
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  @app.get("/")
22
  async def home():
23
  return "Hello, FastAPI! ์ด๊ฑด ๋ฃจํŠธ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค."
 
6
  import requests
7
  from typing import Optional
8
  from fastapi import FastAPI, Request, Header, HTTPException
9
+ import datetime
10
+ import json
11
+ import pytz
12
+ from google.oauth2 import service_account
13
+ from googleapiclient.discovery import build
14
+ from google.auth.transport.requests import Request
15
+ from google.oauth2.credentials import Credentials
16
+ from google_auth_oauthlib.flow import InstalledAppFlow
17
+ import google.generativeai as genai
18
+ from vertexai.generative_models import GenerativeModel
19
+ from dotenv import load_dotenv
20
+
21
+
22
  # sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
23
  from calendar_tool import calendar_main
24
  # .env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.
 
30
  # โš ๏ธ 1๋‹จ๊ณ„์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ 'API ํ† ํฐ'
31
  CHATWORK_API_TOKEN = os.getenv("CHATWORK_API_TOKEN")
32
 
33
+ base_path = os.path.dirname(__file__)
34
+ credentials_path = os.path.join(base_path, 'new_credential.json')
35
+ token_path = os.path.join(base_path, 'new_token.json')
36
+
37
+ if not os.path.exists(credentials_path):
38
+ print(f"'{credentials_path}' ํŒŒ์ผ์ด ์—†์–ด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
39
+ credentials_str = os.getenv('GOOGLE_CREDENTIALS_JSON')
40
+ if credentials_str:
41
+ with open(credentials_path, 'w', encoding='utf-8') as f:
42
+ f.write(credentials_str)
43
+ else:
44
+ print("๊ฒฝ๊ณ : .env ํŒŒ์ผ์— 'GOOGLE_CREDENTIALS_JSON' ๋ณ€์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
45
+
46
+ if not os.path.exists(token_path):
47
+ print(f"'{token_path}' ํŒŒ์ผ์ด ์—†์–ด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
48
+ token_str = os.getenv('GOOGLE_TOKEN_JSON')
49
+ if token_str:
50
+ with open(token_path, 'w', encoding='utf-8') as f:
51
+ f.write(token_str)
52
+
53
+
54
+ CREDENTIALS_FILENAME = credentials_path
55
+ TOKEN_FILENAME = token_path
56
+
57
+ # Google Calendar API ์Šค์ฝ”ํ”„ (์ฝ๊ธฐ ์ „์šฉ)
58
+ SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
59
+ # ๊ธฐ์ค€ ์‹œ๊ฐ„๋Œ€ (GAS์˜ Session.getScriptTimeZone()์— ํ•ด๋‹น)
60
+ # ํ•„์š”์— ๋”ฐ๋ผ 'Asia/Tokyo', 'America/New_York' ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.
61
+ TIMEZONE = 'Asia/Tokyo'
62
+
63
+ rooms = [
64
+ {"capacity": 6, "name": "1F2", "email": os.getenv("GOOGLE_CALENDAR_1F2")},
65
+ {"capacity": 6, "name": "1F3", "email": os.getenv("GOOGLE_CALENDAR_1F3")},
66
+ {"capacity": 12, "name": "3F1", "email": os.getenv("GOOGLE_CALENDAR_3F1")},
67
+ {"capacity": 4, "name": "3F2", "email": os.getenv("GOOGLE_CALENDAR_3F2")},
68
+ {"capacity": 4, "name": "3F3", "email": os.getenv("GOOGLE_CALENDAR_3F3")},
69
+ {"capacity": 4, "name": "3F5", "email": os.getenv("GOOGLE_CALENDAR_3F5")},
70
+ {"capacity": 4, "name": "3F6", "email": os.getenv("GOOGLE_CALENDAR_3F6")},
71
+ {"capacity": 12, "name": "6F1", "email": os.getenv("GOOGLE_CALENDAR_6F1")},
72
+ {"capacity": 4, "name": "6F2", "email": os.getenv("GOOGLE_CALENDAR_6F2")},
73
+ {"capacity": 4, "name": "6F3", "email": os.getenv("GOOGLE_CALENDAR_6F3")},
74
+ {"capacity": 2, "name": "6F5", "email": os.getenv("GOOGLE_CALENDAR_6F5")},
75
+ {"capacity": 2, "name": "6F6", "email": os.getenv("GOOGLE_CALENDAR_6F6")},
76
+ {"capacity": 2, "name": "6F7", "email": os.getenv("GOOGLE_CALENDAR_6F7")},
77
+ {"capacity": 4, "name": "7F2", "email": os.getenv("GOOGLE_CALENDAR_7F2")},
78
+ {"capacity": 4, "name": "7F3", "email": os.getenv("GOOGLE_CALENDAR_7F3")},
79
+ {"capacity": 4, "name": "7F5", "email": os.getenv("GOOGLE_CALENDAR_7F5") },
80
+ {"capacity": 4, "name": "7F6", "email": os.getenv("GOOGLE_CALENDAR_7F6") }
81
+ ]
82
+
83
+ def get_credentials():
84
+ creds = None
85
+ if os.path.exists(TOKEN_FILENAME):
86
+ creds = Credentials.from_authorized_user_file(TOKEN_FILENAME, SCOPES)
87
+
88
+ # ํฌ๋ฆฌ๋ด์…œ์ด ์—†๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ํ”Œ๋กœ์šฐ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
89
+ if not creds or not creds.valid:
90
+ if creds and creds.expired and creds.refresh_token:
91
+ creds.refresh(Request())
92
+ else:
93
+ flow = InstalledAppFlow.from_client_secrets_file(
94
+ CREDENTIALS_FILENAME, SCOPES)
95
+ creds = flow.run_local_server(port=0)
96
+
97
+ # ๋‹ค์Œ ์‹คํ–‰์„ ์œ„ํ•ด ํ† ํฐ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
98
+ with open(TOKEN_FILENAME, 'w') as token:
99
+ token.write(creds.to_json())
100
+ return creds
101
+
102
+
103
+ def check_room_free_slots_over_1h(date_str=None):
104
+ # ํšŒ์˜์‹ค ์ •๋ณด
105
+
106
+ output_lines = []
107
+
108
+ # --- ์ธ์ฆ ๋ฐ ์„œ๋น„์Šค ๋นŒ๋“œ ---
109
+ try:
110
+ creds = get_credentials()
111
+ service = build('calendar', 'v3', credentials=creds)
112
+ except Exception as e:
113
+ print(f"่ช่จผใ‚จใƒฉใƒผ: {e}")
114
+ return
115
+
116
+ # --- ์กฐํšŒ ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ • ---
117
+ # --- ์กฐํšŒ ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ • ---
118
+ tz = pytz.timezone(TIMEZONE)
119
+ now = datetime.datetime.now(tz)
120
+
121
+ if date_str:
122
+ target_date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
123
+ start_time = tz.localize(datetime.datetime.combine(target_date, datetime.time(10, 0)))
124
+ end_time = tz.localize(datetime.datetime.combine(target_date, datetime.time(19, 0)))
125
+ output_lines.append(f"็…งไผšๅฏพ่ฑก: {start_time.strftime('%Y/%m/%d')}")
126
+ else:
127
+ today = now.date()
128
+ start_hour = max(10, now.hour)
129
+ start_time = tz.localize(datetime.datetime.combine(today, datetime.time(start_hour, 0)))
130
+ end_time = tz.localize(datetime.datetime.combine(today, datetime.time(19, 0)))
131
+ output_lines.append(f"็…งไผšๅฏพ่ฑก: {start_time.strftime('%Y/%m/%d')}")
132
+
133
+ if start_time >= end_time:
134
+ output_lines.append("็…งไผšๅฏ่ƒฝใชๆ™‚้–“ใŒใ‚ใ‚Šใพใ›ใ‚“")
135
+ return "\n".join(output_lines)
136
+
137
+ # --- FreeBusy API๋กœ ๋ชจ๋“  ํšŒ์˜์‹ค์˜ ๋ฐ”์œ ์‹œ๊ฐ„ ํ•œ ๋ฒˆ์— ์กฐํšŒ ---
138
+ body = {
139
+ "timeMin": start_time.isoformat(),
140
+ "timeMax": end_time.isoformat(),
141
+ "timeZone": TIMEZONE,
142
+ "items": [{"id": room["email"]} for room in rooms]
143
+ }
144
+
145
+ try:
146
+ freebusy_result = service.freebusy().query(body=body).execute()
147
+ calendars_busy_info = freebusy_result.get('calendars', {})
148
+ except Exception as e:
149
+ return f"FreeBusy API ๅคฑๆ•—: {e}"
150
+
151
+ output_lines.append("-" * 30)
152
+
153
+ # --- ๊ฐ ํšŒ์˜์‹ค๋ณ„๋กœ ๋นˆ ์‹œ๊ฐ„ ๊ณ„์‚ฐ ๋ฐ ์ถœ๋ ฅ ---
154
+ for room in rooms:
155
+ room_email = room['email']
156
+ busy_info = calendars_busy_info.get(room_email, {})
157
+
158
+ if busy_info.get('errors'):
159
+ reason = busy_info['errors'][0].get('reason', 'unknown')
160
+ output_lines.append(f"[{room['name']}] ใ‚ซใƒฌใƒณใƒ€ใƒผ็…งไผšๅคฑๆ•—: {reason}")
161
+ continue
162
+
163
+ busy_slots = busy_info.get('busy', [])
164
+
165
+ cursor = start_time
166
+ free_slots = []
167
+
168
+ for busy in busy_slots:
169
+ busy_start = datetime.datetime.fromisoformat(busy['start'])
170
+ busy_end = datetime.datetime.fromisoformat(busy['end'])
171
+
172
+ if busy_start > cursor:
173
+ free_slots.append({'start': cursor, 'end': busy_start})
174
+
175
+ if busy_end > cursor:
176
+ cursor = busy_end
177
+
178
+ if cursor < end_time:
179
+ free_slots.append({'start': cursor, 'end': end_time})
180
+
181
+ long_free_slots = [
182
+ slot for slot in free_slots
183
+ if (slot['end'] - slot['start']) >= datetime.timedelta(hours=1)
184
+ ]
185
+
186
+ if long_free_slots:
187
+ output_lines.append(f"[{room['name']}] (ๅฎšๅ“ก {room['capacity']}ๅ)")
188
+ for slot in long_free_slots:
189
+ output_lines.append(f" {slot['start'].strftime('%H:%M')} ~ {slot['end'].strftime('%H:%M')}")
190
+
191
+ return "\n".join(output_lines)
192
+
193
+
194
+ def search_calendar(calendar_data , order: str) -> str:
195
+ genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
196
+
197
+ model = genai.GenerativeModel("gemini-1.5-pro")
198
+
199
+ message = f"""ไปฅไธ‹ใŒ็ฉบใๅฎคใฎ็ฉบใ„ใฆใ„ใ‚‹ๆ™‚้–“ๆƒ…ๅ ฑใงใ™ใ€‚\n {calendar_data} ใ“ใฎใƒ‡ใƒผใ‚ฟใ‚’่ฆ‹ใฆ"""
200
+
201
+
202
+ prompt = "{message}{order}".format(message=message, order=order)
203
+ token_info = model.count_tokens(prompt)
204
+ print(f"Prompt Token Count: {token_info.total_tokens}")
205
+
206
+ response = model.generate_content(prompt)
207
+ return response.text if response.text else "็”Ÿๆˆๅคฑๆ•—"
208
+
209
+
210
+ def calendar_main(input_string: str):
211
+ try:
212
+ parts = input_string.split()
213
+ if len(parts) < 3 or parts[0].lower() != 'ไผš่ญฐๅฎค':
214
+ return
215
+
216
+ # ๋‚ ์งœ ํŒŒ์‹ฑ ๋ฐ ํฌ๋งทํŒ…
217
+ date_input = parts[1]
218
+ formatted_date= ""
219
+ if date_input == "ใ™ใ":
220
+ formatted_date = ""
221
+ elif len(date_input) != 8 or not date_input.isdigit():
222
+ print("ๆ—ฅไป˜ใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆใŒๆญฃใ—ใใ‚ใ‚Šใพใ›ใ‚“ YYYYMMDD ใซใ—ใฆใใ ใ•ใ„ใ€‚")
223
+ return
224
+ else:
225
+ formatted_date = f"{date_input[:4]}-{date_input[4:6]}-{date_input[6:]}"
226
+
227
+ # ์ธ์›์ˆ˜ ํŒŒ์‹ฑ
228
+ capacity_input = parts[2]
229
+ if not capacity_input.isdigit():
230
+ print("ไบบๆ•ฐใฏๆ•ฐๅญ—ใฎใฟๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚")
231
+ return
232
+ min_capacity = int(capacity_input)
233
+
234
+
235
+ # 4๋ฒˆ์งธ ์ธ์ž๊ฐ€ ์žˆ์œผ๋ฉด ์ทจ๋“
236
+ optional_arg = ""
237
+ if len(parts) > 3:
238
+ optional_arg = " ".join(parts[3:])
239
+ print(f"่ฟฝๅŠ ใ‚ชใƒ—ใ‚ทใƒงใƒณ: '{optional_arg}'")
240
+
241
+
242
+
243
+
244
+ except Exception as e:
245
+ print(f"ใ‚จใƒฉใƒผ็™บ็”Ÿ: {e}")
246
+ return
247
+
248
+ free_rooms = check_room_free_slots_over_1h(formatted_date)
249
+ if free_rooms:
250
+ response = search_calendar(free_rooms, f"{formatted_date}ใซ{min_capacity}ๅใŒๅ…ฅใ‚Œใ‚‹ไผš่ญฐๅฎคใ‚’ๆ•™ใˆใฆใใ ใ•ใ„{optional_arg}ใ€‚ๅ›ž็ญ”ใฏใ‚ทใƒณใƒ—ใƒซใซ[ไผš่ญฐๅฎคๅ] ๅฎšๅ“ก ็ฉบใๆ™‚้–“ใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆใงใ—ใฆใใ ใ•ใ„")
251
+ return response
252
+ else:
253
+ return
254
+
255
+
256
  @app.get("/")
257
  async def home():
258
  return "Hello, FastAPI! ์ด๊ฑด ๋ฃจํŠธ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค."
app/calendar_tool.py DELETED
@@ -1,240 +0,0 @@
1
- import os
2
- import sys
3
- import datetime
4
- import json
5
- import pytz
6
- from google.oauth2 import service_account
7
- from googleapiclient.discovery import build
8
- from google.auth.transport.requests import Request
9
- from google.oauth2.credentials import Credentials
10
- from google_auth_oauthlib.flow import InstalledAppFlow
11
- import google.generativeai as genai
12
- from vertexai.generative_models import GenerativeModel
13
- from dotenv import load_dotenv
14
-
15
- load_dotenv()
16
- base_path = os.path.dirname(__file__)
17
- credentials_path = os.path.join(base_path, 'new_credential.json')
18
- token_path = os.path.join(base_path, 'new_token.json')
19
-
20
- if not os.path.exists(credentials_path):
21
- print(f"'{credentials_path}' ํŒŒ์ผ์ด ์—†์–ด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
22
- credentials_str = os.getenv('GOOGLE_CREDENTIALS_JSON')
23
- if credentials_str:
24
- with open(credentials_path, 'w', encoding='utf-8') as f:
25
- f.write(credentials_str)
26
- else:
27
- print("๊ฒฝ๊ณ : .env ํŒŒ์ผ์— 'GOOGLE_CREDENTIALS_JSON' ๋ณ€์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
28
-
29
- if not os.path.exists(token_path):
30
- print(f"'{token_path}' ํŒŒ์ผ์ด ์—†์–ด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
31
- token_str = os.getenv('GOOGLE_TOKEN_JSON')
32
- if token_str:
33
- with open(token_path, 'w', encoding='utf-8') as f:
34
- f.write(token_str)
35
-
36
-
37
- CREDENTIALS_FILENAME = credentials_path
38
- TOKEN_FILENAME = token_path
39
-
40
- # Google Calendar API ์Šค์ฝ”ํ”„ (์ฝ๊ธฐ ์ „์šฉ)
41
- SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
42
- # ๊ธฐ์ค€ ์‹œ๊ฐ„๋Œ€ (GAS์˜ Session.getScriptTimeZone()์— ํ•ด๋‹น)
43
- # ํ•„์š”์— ๋”ฐ๋ผ 'Asia/Tokyo', 'America/New_York' ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.
44
- TIMEZONE = 'Asia/Tokyo'
45
-
46
- rooms = [
47
- {"capacity": 6, "name": "1F2", "email": os.getenv("GOOGLE_CALENDAR_1F2")},
48
- {"capacity": 6, "name": "1F3", "email": os.getenv("GOOGLE_CALENDAR_1F3")},
49
- {"capacity": 12, "name": "3F1", "email": os.getenv("GOOGLE_CALENDAR_3F1")},
50
- {"capacity": 4, "name": "3F2", "email": os.getenv("GOOGLE_CALENDAR_3F2")},
51
- {"capacity": 4, "name": "3F3", "email": os.getenv("GOOGLE_CALENDAR_3F3")},
52
- {"capacity": 4, "name": "3F5", "email": os.getenv("GOOGLE_CALENDAR_3F5")},
53
- {"capacity": 4, "name": "3F6", "email": os.getenv("GOOGLE_CALENDAR_3F6")},
54
- {"capacity": 12, "name": "6F1", "email": os.getenv("GOOGLE_CALENDAR_6F1")},
55
- {"capacity": 4, "name": "6F2", "email": os.getenv("GOOGLE_CALENDAR_6F2")},
56
- {"capacity": 4, "name": "6F3", "email": os.getenv("GOOGLE_CALENDAR_6F3")},
57
- {"capacity": 2, "name": "6F5", "email": os.getenv("GOOGLE_CALENDAR_6F5")},
58
- {"capacity": 2, "name": "6F6", "email": os.getenv("GOOGLE_CALENDAR_6F6")},
59
- {"capacity": 2, "name": "6F7", "email": os.getenv("GOOGLE_CALENDAR_6F7")},
60
- {"capacity": 4, "name": "7F2", "email": os.getenv("GOOGLE_CALENDAR_7F2")},
61
- {"capacity": 4, "name": "7F3", "email": os.getenv("GOOGLE_CALENDAR_7F3")},
62
- {"capacity": 4, "name": "7F5", "email": os.getenv("GOOGLE_CALENDAR_7F5") },
63
- {"capacity": 4, "name": "7F6", "email": os.getenv("GOOGLE_CALENDAR_7F6") }
64
- ]
65
-
66
- def get_credentials():
67
- creds = None
68
- if os.path.exists(TOKEN_FILENAME):
69
- creds = Credentials.from_authorized_user_file(TOKEN_FILENAME, SCOPES)
70
-
71
- # ํฌ๋ฆฌ๋ด์…œ์ด ์—†๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ํ”Œ๋กœ์šฐ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
72
- if not creds or not creds.valid:
73
- if creds and creds.expired and creds.refresh_token:
74
- creds.refresh(Request())
75
- else:
76
- flow = InstalledAppFlow.from_client_secrets_file(
77
- CREDENTIALS_FILENAME, SCOPES)
78
- creds = flow.run_local_server(port=0)
79
-
80
- # ๋‹ค์Œ ์‹คํ–‰์„ ์œ„ํ•ด ํ† ํฐ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
81
- with open(TOKEN_FILENAME, 'w') as token:
82
- token.write(creds.to_json())
83
- return creds
84
-
85
-
86
- def check_room_free_slots_over_1h(date_str=None):
87
- # ํšŒ์˜์‹ค ์ •๋ณด
88
-
89
- output_lines = []
90
-
91
- # --- ์ธ์ฆ ๋ฐ ์„œ๋น„์Šค ๋นŒ๋“œ ---
92
- try:
93
- creds = get_credentials()
94
- service = build('calendar', 'v3', credentials=creds)
95
- except Exception as e:
96
- print(f"่ช่จผใ‚จใƒฉใƒผ: {e}")
97
- return
98
-
99
- # --- ์กฐํšŒ ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ • ---
100
- # --- ์กฐํšŒ ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ • ---
101
- tz = pytz.timezone(TIMEZONE)
102
- now = datetime.datetime.now(tz)
103
-
104
- if date_str:
105
- target_date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
106
- start_time = tz.localize(datetime.datetime.combine(target_date, datetime.time(10, 0)))
107
- end_time = tz.localize(datetime.datetime.combine(target_date, datetime.time(19, 0)))
108
- output_lines.append(f"็…งไผšๅฏพ่ฑก: {start_time.strftime('%Y/%m/%d')}")
109
- else:
110
- today = now.date()
111
- start_hour = max(10, now.hour)
112
- start_time = tz.localize(datetime.datetime.combine(today, datetime.time(start_hour, 0)))
113
- end_time = tz.localize(datetime.datetime.combine(today, datetime.time(19, 0)))
114
- output_lines.append(f"็…งไผšๅฏพ่ฑก: {start_time.strftime('%Y/%m/%d')}")
115
-
116
- if start_time >= end_time:
117
- output_lines.append("็…งไผšๅฏ่ƒฝใชๆ™‚้–“ใŒใ‚ใ‚Šใพใ›ใ‚“")
118
- return "\n".join(output_lines)
119
-
120
- # --- FreeBusy API๋กœ ๋ชจ๋“  ํšŒ์˜์‹ค์˜ ๋ฐ”์œ ์‹œ๊ฐ„ ํ•œ ๋ฒˆ์— ์กฐํšŒ ---
121
- body = {
122
- "timeMin": start_time.isoformat(),
123
- "timeMax": end_time.isoformat(),
124
- "timeZone": TIMEZONE,
125
- "items": [{"id": room["email"]} for room in rooms]
126
- }
127
-
128
- try:
129
- freebusy_result = service.freebusy().query(body=body).execute()
130
- calendars_busy_info = freebusy_result.get('calendars', {})
131
- except Exception as e:
132
- return f"FreeBusy API ๅคฑๆ•—: {e}"
133
-
134
- output_lines.append("-" * 30)
135
-
136
- # --- ๊ฐ ํšŒ์˜์‹ค๋ณ„๋กœ ๋นˆ ์‹œ๊ฐ„ ๊ณ„์‚ฐ ๋ฐ ์ถœ๋ ฅ ---
137
- for room in rooms:
138
- room_email = room['email']
139
- busy_info = calendars_busy_info.get(room_email, {})
140
-
141
- if busy_info.get('errors'):
142
- reason = busy_info['errors'][0].get('reason', 'unknown')
143
- output_lines.append(f"[{room['name']}] ใ‚ซใƒฌใƒณใƒ€ใƒผ็…งไผšๅคฑๆ•—: {reason}")
144
- continue
145
-
146
- busy_slots = busy_info.get('busy', [])
147
-
148
- cursor = start_time
149
- free_slots = []
150
-
151
- for busy in busy_slots:
152
- busy_start = datetime.datetime.fromisoformat(busy['start'])
153
- busy_end = datetime.datetime.fromisoformat(busy['end'])
154
-
155
- if busy_start > cursor:
156
- free_slots.append({'start': cursor, 'end': busy_start})
157
-
158
- if busy_end > cursor:
159
- cursor = busy_end
160
-
161
- if cursor < end_time:
162
- free_slots.append({'start': cursor, 'end': end_time})
163
-
164
- long_free_slots = [
165
- slot for slot in free_slots
166
- if (slot['end'] - slot['start']) >= datetime.timedelta(hours=1)
167
- ]
168
-
169
- if long_free_slots:
170
- output_lines.append(f"[{room['name']}] (ๅฎšๅ“ก {room['capacity']}ๅ)")
171
- for slot in long_free_slots:
172
- output_lines.append(f" {slot['start'].strftime('%H:%M')} ~ {slot['end'].strftime('%H:%M')}")
173
-
174
- return "\n".join(output_lines)
175
-
176
-
177
- def search_calendar(calendar_data , order: str) -> str:
178
- genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
179
-
180
- model = genai.GenerativeModel("gemini-1.5-pro")
181
-
182
- message = f"""ไปฅไธ‹ใŒ็ฉบใๅฎคใฎ็ฉบใ„ใฆใ„ใ‚‹ๆ™‚้–“ๆƒ…ๅ ฑใงใ™ใ€‚\n {calendar_data} ใ“ใฎใƒ‡ใƒผใ‚ฟใ‚’่ฆ‹ใฆ"""
183
-
184
-
185
- prompt = "{message}{order}".format(message=message, order=order)
186
- token_info = model.count_tokens(prompt)
187
- print(f"Prompt Token Count: {token_info.total_tokens}")
188
-
189
- response = model.generate_content(prompt)
190
- return response.text if response.text else "็”Ÿๆˆๅคฑๆ•—"
191
-
192
-
193
- def calendar_main(input_string: str):
194
- try:
195
- parts = input_string.split()
196
- if len(parts) < 3 or parts[0].lower() != 'ไผš่ญฐๅฎค':
197
- return
198
-
199
- # ๋‚ ์งœ ํŒŒ์‹ฑ ๋ฐ ํฌ๋งทํŒ…
200
- date_input = parts[1]
201
- formatted_date= ""
202
- if date_input == "ใ™ใ":
203
- formatted_date = ""
204
- elif len(date_input) != 8 or not date_input.isdigit():
205
- print("ๆ—ฅไป˜ใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆใŒๆญฃใ—ใใ‚ใ‚Šใพใ›ใ‚“ YYYYMMDD ใซใ—ใฆใใ ใ•ใ„ใ€‚")
206
- return
207
- else:
208
- formatted_date = f"{date_input[:4]}-{date_input[4:6]}-{date_input[6:]}"
209
-
210
- # ์ธ์›์ˆ˜ ํŒŒ์‹ฑ
211
- capacity_input = parts[2]
212
- if not capacity_input.isdigit():
213
- print("ไบบๆ•ฐใฏๆ•ฐๅญ—ใฎใฟๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚")
214
- return
215
- min_capacity = int(capacity_input)
216
-
217
-
218
- # 4๋ฒˆ์งธ ์ธ์ž๊ฐ€ ์žˆ์œผ๋ฉด ์ทจ๋“
219
- optional_arg = ""
220
- if len(parts) > 3:
221
- optional_arg = " ".join(parts[3:])
222
- print(f"่ฟฝๅŠ ใ‚ชใƒ—ใ‚ทใƒงใƒณ: '{optional_arg}'")
223
-
224
-
225
-
226
-
227
- except Exception as e:
228
- print(f"ใ‚จใƒฉใƒผ็™บ็”Ÿ: {e}")
229
- return
230
-
231
- free_rooms = check_room_free_slots_over_1h(formatted_date)
232
- if free_rooms:
233
- response = search_calendar(free_rooms, f"{formatted_date}ใซ{min_capacity}ๅใŒๅ…ฅใ‚Œใ‚‹ไผš่ญฐๅฎคใ‚’ๆ•™ใˆใฆใใ ใ•ใ„{optional_arg}ใ€‚ๅ›ž็ญ”ใฏใ‚ทใƒณใƒ—ใƒซใซ[ไผš่ญฐๅฎคๅ] ๅฎšๅ“ก ็ฉบใๆ™‚้–“ใฎใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆใงใ—ใฆใใ ใ•ใ„")
234
- return response
235
- else:
236
- return
237
-
238
-
239
-
240
-