Rene0119 commited on
Commit
a48d4df
·
0 Parent(s):

Initial commit after filter-repo

Browse files
Files changed (7) hide show
  1. .gitattributes +35 -0
  2. .gitignore +3 -0
  3. Dockerfile +30 -0
  4. Linebot Drug Use.json +13 -0
  5. README.md +10 -0
  6. app.py +261 -0
  7. requirements.txt +13 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ medicine.db
2
+ .DS_Store
3
+ .env
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Python 映像作為基礎
2
+ FROM python:3.9-slim
3
+
4
+ # 設定工作目錄
5
+ WORKDIR /app
6
+
7
+ # 將 requirements.txt 複製到工作目錄
8
+ COPY requirements.txt .
9
+
10
+ # 安裝依賴套件
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # 將專案中的所有檔案複製到工作目錄
14
+ COPY . .
15
+
16
+ # 新增:更改 /app 目錄的權限,使其對所有使用者可寫
17
+ RUN chmod -R 777 /app
18
+
19
+ # 設定環境變數 PORT (Hugging Face Spaces 通常會提供這個環境變數)
20
+ # 您的 app.py 應該從 os.getenv("PORT", "7860") 讀取端口
21
+ # 如果您的 app.py 中寫死了端口,例如 5000,這裡可以改為 EXPOSE 5000
22
+ # 但最好是讓 Flask 監聽 $PORT
23
+ ENV PORT 7860
24
+
25
+ # 開放應用程式運行的端口 (與上面 ENV PORT 一致,或者您 Flask 監聽的端口)
26
+ EXPOSE 7860
27
+
28
+ # 執行應用程式的指令
29
+ # 確保您的 app.py 中有 if __name__ == "__main__": app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))
30
+ CMD ["python", "app.py"]
Linebot Drug Use.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "linebot-druguse",
4
+ "private_key_id": "b80390a9e6d6c96a18e564e65515ace30683018d",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQS9MpQmXme4IV\nrx2RkWH7jy0JNc9sdxY2kovOfth9ONAyHjQ453CaQtX7f80Sk59pggN3nmBzS1lE\nXYDfmrL+FfIa8xDXgsjytJq6A43ZRkahnpvp8wMx0hFa9nNZYNT2KYbnXpDh/Pdb\nRaeWa06YtxvrOsX1M9mI3vfth8sfh3935NHIshBRIVFBZbhEci9qkMMKW/WvPHcu\nfAystdCvf3nDpwyyjA6Yy8tCVj05tDm1qGpC4rdyRex1ZR7Kpn/9ctDFyBu+lJYi\niWJcxlBr170Jo7bq//9GUcdzQO07egpvo6EubI58jFeuTYdNIT1iMgP6jYn65Ehw\n2Ru+kOAtAgMBAAECggEADk1VMtUiNzdonkcKHiDlTAHPyz0Ei0jI/8HDs4/I8HOF\nHhqDUPCZQ3T5NDMuraAbwcGW2IY0JAPQMw4YMiJGjdo2vQwkA6bcAog37d5JGtpa\nXAYOjruBebtYDDl1qvUnt5Lwi59AuPem31ToIH9T4/oCAtPkBvBYDIUXcRnnbqMT\naP66kMgindSkUOAXfHpveFd53IG04jSX7Tzi87jbvcsI2mmKnA30qAYruLjKtk0G\nUJ2ANdylW73aiBEprWayrkyZRxGnKJeXDwIf/j4d5Kd9HDG827LVirqXZKk2gmt+\nAo81UFfYABRK90ibsy5eynnZ5/j8XX8/qdPUoGeFcQKBgQD37a9wyr6y7hdtr7Pz\nTwSqJ7R88niLgwmtBbe8CBaA5uCijS/B7mvZWA65vRH0T2m56DH8ydich8gzwG7r\n8E1d3W/51iCbI4qaSeDn7rjqcDR1ujeh9KK3reJdkChkbBfesNBCYekAcRleb66q\nJekcXHDwK0MMKwQxwZlCOUXH0QKBgQDXE9Tj3KnRDjhRKDnIOdQFLANBbLFyqerA\nASdUpJOCUl+NQQ2yxkFmmfAF6piwhoViGlJpeEoDYSHrNfBqR2/e0dVJDD7V6/5T\ntQ20TwgV7ZopP2qUrxRuwX9XMaGQJ9LTkVux/1wbGSXMwc8tuKn0ALnAAbOdRLTi\nw2+rFcpFnQKBgQDXCuY9It29PR49GUUwKL2XCNFX/sfn2kSuOmNfSHScYaU92NAv\nmakZ33MGT6QfEDJX+bpB1KF5Q5y2ppaPNuMkI0lYng2yAmXZ5XKaOUlSrvgRlO9k\nHlh0kPHizYL7d3ZMxXgd2+bJZ0/lxWDuW+uRmeyrpBqHl1gQACf6dMq9wQKBgDf6\nKGF50BTiD5RH1Oog6ubBssM3Z+PBB6Eba8Ii52V2zPm1obwSz3jFveu8SCtBh7ul\nwCdFMHY6tJpEOqNenmYu1MUjU8NXnaTprXxtSVxzr7jmkXXLL5U39U2bVEJxqvR3\nk5b7MFy5KdZl5ucudcVU3wNzLe1TsYSHeyvbCv8dAoGAEdu6yoOoGxwe78rW7QvG\nTyS1xtSlP7ZMvdm3UuSLPGa8L6B77qHAxUbaYRjfHjgNg2whtxYKrcnrYmg5NYR0\nWmI/ldXhQTztBpo4Xmab3qNoU1dyBVUNgfeb5h1fvUEBmaWJUvtec1RdeBz8SSbX\nOX6WwlSDUzxIcOcdGC3u4Ek=\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "linebot-db-accessor@linebot-druguse.iam.gserviceaccount.com",
7
+ "client_id": "108614875347090756394",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/linebot-db-accessor%40linebot-druguse.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: DrugUse LineBot
3
+ emoji: 👀
4
+ colorFrom: pink
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ 這是我的 DrugUse LineBot 專案。
app.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import requests
3
+ import os
4
+ from flask import Flask, request, abort
5
+ from linebot.v3.webhook import WebhookParser, WebhookHandler
6
+ from linebot.v3.webhooks import MessageEvent, TextMessageContent
7
+ from linebot.v3.messaging import MessagingApi, Configuration, ApiClient
8
+ from linebot.v3.messaging.models import (
9
+ TextMessage, ReplyMessageRequest,
10
+ FlexMessage, FlexBubble, FlexBox, FlexText, FlexButton, URIAction,
11
+ QuickReply, QuickReplyItem, LocationAction
12
+ )
13
+ from linebot.v3.exceptions import InvalidSignatureError
14
+
15
+ import io
16
+ import json
17
+ from dotenv import load_dotenv
18
+
19
+ # --- Google Drive API 相關導入 ---
20
+ from google.oauth2 import service_account # 修改這裡
21
+ from googleapiclient.discovery import build
22
+ from googleapiclient.http import MediaIoBaseDownload
23
+ # --- END Google Drive API 相關導入 ---
24
+
25
+ load_dotenv()
26
+
27
+ app = Flask(__name__)
28
+
29
+ # 從環境變數讀取 LINE Bot 設定
30
+ LINE_CHANNEL_SECRET = os.getenv("LINE_CHANNEL_SECRET", "YOUR_FALLBACK_SECRET") # 建議提供一個預設值或錯誤處理
31
+ LINE_CHANNEL_ACCESS_TOKEN = os.getenv("LINE_CHANNEL_ACCESS_TOKEN", "YOUR_FALLBACK_TOKEN")
32
+
33
+ # --- 資料庫路徑設定 ---
34
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
35
+ DB_FILENAME = "medicine.db"
36
+ DB_PATH = os.path.join(BASE_DIR, DB_FILENAME) # 確保 DB_PATH 指向容器內的預期路徑
37
+ # --- END 資料庫路徑設定 ---
38
+
39
+ # --- Google Drive 下載函式 ---
40
+ def download_db_from_google_drive():
41
+ print("Attempting to download database from Google Drive...")
42
+ google_creds_json_str = os.getenv("GOOGLE_CREDENTIALS_JSON")
43
+ file_id = os.getenv("GOOGLE_DRIVE_FILE_ID")
44
+
45
+ if not google_creds_json_str:
46
+ print("Error: GOOGLE_CREDENTIALS_JSON secret not found.")
47
+ return False
48
+ if not file_id:
49
+ print("Error: GOOGLE_DRIVE_FILE_ID secret not found.")
50
+ return False
51
+
52
+ try:
53
+ # 將 JSON 字串轉換為字典
54
+ creds_info = json.loads(google_creds_json_str)
55
+ # 從服務帳戶資訊建立憑證
56
+ creds = service_account.Credentials.from_service_account_info(
57
+ creds_info,
58
+ scopes=['https://www.googleapis.com/auth/drive.readonly'] # 只需要唯讀權限
59
+ )
60
+ drive_service = build('drive', 'v3', credentials=creds)
61
+
62
+ request_dl = drive_service.files().get_media(fileId=file_id)
63
+ # fh = io.BytesIO() # 記憶體中處理
64
+ # downloader = MediaIoBaseDownload(fh, request_dl)
65
+
66
+ # 直接寫入檔案
67
+ with open(DB_PATH, 'wb') as fh:
68
+ downloader = MediaIoBaseDownload(fh, request_dl)
69
+ done = False
70
+ while done is False:
71
+ status, done = downloader.next_chunk()
72
+ print(f"Download {int(status.progress() * 100)}%.")
73
+ print(f"Database '{DB_FILENAME}' downloaded successfully to '{DB_PATH}'.")
74
+ return True
75
+ except Exception as e:
76
+ print(f"Error downloading database from Google Drive: {e}")
77
+ return False
78
+ # --- END Google Drive 下載函式 ---
79
+
80
+ # --- 在應用程式啟動時執行資料庫下載 ---
81
+ DOWNLOAD_SUCCESS = False # Initialize
82
+ # Check if running in Hugging Face Space or if secrets are generally available
83
+ # In HF Spaces, secrets are environment variables. Locally, .env would be used.
84
+ if os.getenv("GOOGLE_CREDENTIALS_JSON") and os.getenv("GOOGLE_DRIVE_FILE_ID"):
85
+ print("Found Google Drive credentials, attempting download...")
86
+ DOWNLOAD_SUCCESS = download_db_from_google_drive()
87
+ else:
88
+ print("Warning: GOOGLE_CREDENTIALS_JSON or GOOGLE_DRIVE_FILE_ID not found in environment. Skipping DB download.")
89
+ print("If running locally, ensure they are in your .env file or environment.")
90
+ print("If on Hugging Face, ensure secrets are set in Space settings.")
91
+
92
+ if not DOWNLOAD_SUCCESS:
93
+ print("CRITICAL: Database download failed or was skipped. The application might not function as expected if the database is required at startup.")
94
+ # --- END 應用程式啟動時執行資料庫下載 ---
95
+
96
+ print(f"LINE_CHANNEL_SECRET: {LINE_CHANNEL_SECRET}")
97
+ print(f"LINE_CHANNEL_ACCESS_TOKEN: {LINE_CHANNEL_ACCESS_TOKEN}")
98
+ #print(f"GOOGLE_MAP_API_KEY: {GOOGLE_MAP_API_KEY}")
99
+
100
+ configuration = Configuration(access_token=LINE_CHANNEL_ACCESS_TOKEN)
101
+ parser = WebhookParser(LINE_CHANNEL_SECRET)
102
+ handler = WebhookHandler(LINE_CHANNEL_SECRET)
103
+
104
+ @app.route("/callback", methods=["POST"])
105
+ def callback():
106
+
107
+ # 檢查資料庫是否已成功下載 (可選,但建議)
108
+ if not DOWNLOAD_SUCCESS and not os.path.exists(DB_PATH):
109
+ print("Database not available, aborting callback.")
110
+ abort(500) # 或返回一個提示用戶稍後再試的訊息
111
+
112
+ signature = request.headers.get("X-Line-Signature", "")
113
+ body = request.get_data(as_text=True)
114
+
115
+ try:
116
+ events = parser.parse(body, signature)
117
+ except InvalidSignatureError:
118
+ abort(400)
119
+ except Exception as e:
120
+ print("Webhook parse error:", e)
121
+ abort(400)
122
+
123
+ with ApiClient(configuration) as api_client:
124
+ messaging_api = MessagingApi(api_client)
125
+
126
+ for event in events:
127
+ if event.type == "message":
128
+ if event.message.type == "text":
129
+ user_input = event.message.text.strip()
130
+ print("📨 收到訊息:", user_input)
131
+
132
+ if user_input == "查詢藥品":
133
+ reply_text = "請輸入藥品名稱,例如:口服感冒藥"
134
+ reply_request = ReplyMessageRequest(
135
+ reply_token=event.reply_token,
136
+ messages=[TextMessage(text=reply_text)]
137
+ )
138
+ messaging_api.reply_message(reply_message_request=reply_request)
139
+
140
+ elif "查詢藥局" in user_input:
141
+ quick_reply = QuickReply(
142
+ items=[QuickReplyItem(action=LocationAction(label="傳送我的位置"))]
143
+ )
144
+ reply_request = ReplyMessageRequest(
145
+ reply_token=event.reply_token,
146
+ messages=[TextMessage(text="請點選下方按鈕傳送你的位置,我才能幫你找附近藥局喔~", quick_reply=quick_reply)]
147
+ )
148
+ messaging_api.reply_message(reply_message_request=reply_request)
149
+
150
+ else:
151
+ # 直接當作藥品名稱查詢
152
+ medicine_name = user_input.lower()
153
+ try:
154
+ conn = sqlite3.connect(DB_PATH)
155
+ cursor = conn.cursor()
156
+ query = """
157
+ SELECT DISTINCT 中文品名, 英文品名, 適應症
158
+ FROM drugs
159
+ WHERE LOWER(中文品名) LIKE ?
160
+ LIMIT 3
161
+ """
162
+ cursor.execute(query, ('%' + medicine_name + '%',))
163
+ rows = cursor.fetchall()
164
+ conn.close()
165
+
166
+ if not rows:
167
+ reply_text = f"找不到與「{medicine_name}」相關的藥品。"
168
+ else:
169
+ reply_text = ""
170
+ for row in rows:
171
+ reply_text += (
172
+ f"🔹 中文品名:{row[0]}\n"
173
+ f"📌 英文品名:{row[1]}\n"
174
+ f"📄 適應症:{row[2]}\n\n"
175
+ )
176
+ except Exception as e:
177
+ reply_text = f"⚠️ 查詢資料時發生錯誤:{str(e)}"
178
+
179
+ reply_request = ReplyMessageRequest(
180
+ reply_token=event.reply_token,
181
+ messages=[TextMessage(text=reply_text.strip())]
182
+ )
183
+ messaging_api.reply_message(reply_message_request=reply_request)
184
+
185
+ elif event.message.type == "location":
186
+ user_lat = event.message.latitude
187
+ user_lng = event.message.longitude
188
+
189
+ nearby_url = (
190
+ f"https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
191
+ f"location={user_lat},{user_lng}&radius=1000&type=pharmacy&key={GOOGLE_MAP_API_KEY}"
192
+ )
193
+ nearby_res = requests.get(nearby_url).json()
194
+
195
+ if not nearby_res.get('results'):
196
+ reply_request = ReplyMessageRequest(
197
+ reply_token=event.reply_token,
198
+ messages=[TextMessage(text="附近找不到藥局")]
199
+ )
200
+ messaging_api.reply_message(reply_message_request=reply_request)
201
+ continue
202
+
203
+ place = nearby_res['results'][0]
204
+ place_id = place['place_id']
205
+ name = place.get('name', '藥局名稱未知')
206
+ location = place['geometry']['location']
207
+ dest_lat, dest_lng = location['lat'], location['lng']
208
+
209
+ details_url = (
210
+ f"https://maps.googleapis.com/maps/api/place/details/json?"
211
+ f"place_id={place_id}&fields=name,formatted_phone_number&key={GOOGLE_MAP_API_KEY}"
212
+ )
213
+ details_res = requests.get(details_url).json()
214
+ phone = details_res.get('result', {}).get('formatted_phone_number', '電話不詳')
215
+
216
+ dist_url = (
217
+ f"https://maps.googleapis.com/maps/api/distancematrix/json?"
218
+ f"origins={user_lat},{user_lng}&destinations={dest_lat},{dest_lng}&key={GOOGLE_MAP_API_KEY}"
219
+ )
220
+ dist_res = requests.get(dist_url).json()
221
+ distance = dist_res['rows'][0]['elements'][0]['distance']['text']
222
+
223
+ map_url = f"https://www.google.com/maps/search/?api=1&query={dest_lat},{dest_lng}"
224
+
225
+ bubble = FlexBubble(
226
+ body=FlexBox(
227
+ layout="vertical",
228
+ contents=[
229
+ FlexText(text=name, weight="bold", size="lg"),
230
+ FlexText(text=f"電話:{phone}", size="sm", color="#555555"),
231
+ FlexText(text=f"距離:{distance}", size="sm", color="#777777"),
232
+ ],
233
+ ),
234
+ footer=FlexBox(
235
+ layout="vertical",
236
+ contents=[
237
+ FlexButton(
238
+ style="link",
239
+ height="sm",
240
+ action=URIAction(label="地圖導航", uri=map_url),
241
+ )
242
+ ],
243
+ ),
244
+ )
245
+
246
+ flex_message = FlexMessage(
247
+ alt_text="附近藥局推薦",
248
+ contents=bubble
249
+ )
250
+
251
+ reply_request = ReplyMessageRequest(
252
+ reply_token=event.reply_token,
253
+ messages=[flex_message]
254
+ )
255
+ messaging_api.reply_message(reply_message_request=reply_request)
256
+
257
+ return "OK"
258
+
259
+ if __name__ == "__main__":
260
+ port = int(os.environ.get("PORT", 7860)) # 讀取環境變數 PORT,預設為 7860
261
+ app.run(host="0.0.0.0", port=port, debug=False)
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ line-bot-sdk
3
+ transformers
4
+ torch
5
+ # torchvision # 如果您確定沒有用到 torchvision,可以考慮移除或註解掉
6
+ # torchaudio # 同上
7
+ diffusers
8
+ accelerate
9
+ Pillow
10
+ python-dotenv
11
+ google-api-python-client
12
+ google-auth-httplib2
13
+ google-auth-oauthlib