Jiangxz commited on
Commit
ff10289
·
verified ·
1 Parent(s): 9692bfd

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +287 -0
  2. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # 財政部財政資訊中心 江信宗
3
+
4
+ import gradio as gr
5
+ import os
6
+ import json
7
+ from PIL import Image
8
+ import resend
9
+ import time
10
+ from openai import OpenAI
11
+ import io
12
+ import base64
13
+ import requests
14
+ import re
15
+
16
+ def scam(image, api_key):
17
+ start_time = time.time()
18
+ if image is not None:
19
+ # 將 PIL Image 轉換為 bytes
20
+ img_byte_arr = io.BytesIO()
21
+ image.save(img_byte_arr, format='PNG')
22
+ img_byte_arr = img_byte_arr.getvalue()
23
+ # 轉換為 base64
24
+ img_b64_str = base64.b64encode(img_byte_arr).decode('utf-8')
25
+ img_type = "image/png"
26
+ else:
27
+ gr.Warning(f"請上傳圖片!")
28
+ return "請上傳圖片!", True
29
+ if not api_key:
30
+ resend.api_key = os.environ["YOUR_API_TOKEN"]
31
+ params: resend.Emails.SendParams = {
32
+ "from": "Scam_API <onboarding@resend.dev>",
33
+ "to": ["antivir7@gmail.com"],
34
+ "subject": "訊息分析API",
35
+ "html": f"""
36
+ <strong>詐騙訊息分析API</strong>
37
+ """,
38
+ }
39
+ try:
40
+ email_response = resend.Emails.send(params)
41
+ print(f"Email sent successfully. Response:{email_response}")
42
+ api_key = os.getenv("YOUR_API_KEY")
43
+ except Exception as e:
44
+ print(f"Failed to send email:{str(e)}")
45
+ client = OpenAI(
46
+ api_key=api_key,
47
+ base_url="https://free.gpt.ge/v1",
48
+ )
49
+ user_prompt = f"""請分析user提供的內容並以廣告詐騙判斷標準進行連結分析、廣告類寫作風格和連網查證內容事實合理性,整合廣告詐騙判斷標準並做詐騙內容分析,如果檢索不到網址或電話的訊息則寫「無」,請實的說明,不要試圖編造答案,最終判斷以上內容是廣告訊息或詐騙訊息並告訴我你對答案的信心分數。
50
+
51
+ Remember: 廣告詐騙判斷標準:
52
+  1. 短網址
53
+  2. 連結內容為邀請加入
54
+  3. 行動呼籲
55
+  4. 高回報承諾
56
+  5. 內容為政府訊息,網址應有「gov.tw」
57
+  6. 電話國碼非886
58
+
59
+ # 回應格式:
60
+  ## 短網址:[是/否/無]
61
+  ## 連結:
62
+  ## 電話:
63
+  ## 詐騙內容分析:
64
+  ## 詐騙判斷:
65
+  ## 信心水準:[0-100] %
66
+
67
+ 電話格式範例:
68
+ 09XXXXXXXX
69
+ 09XX-XXXXXX
70
+ 8869XXXXXXXX
71
+ 8869XX-XXXXXX
72
+ \+(?:\d{2,3})\s*(?:\d{2,3}[-\s]*\d{3}[-\s]*\d{3,4})
73
+ """
74
+ try:
75
+ gr.Info("開始分析詐騙中....")
76
+ response = client.chat.completions.create(
77
+ model="gpt-4o-mini",
78
+ messages=[
79
+ {
80
+ "role": "user",
81
+ "content": [
82
+ {"type": "text", "text": user_prompt},
83
+ {
84
+ "type": "image_url",
85
+ "image_url": {"url": f"data:{img_type};base64,{img_b64_str}"},
86
+ },
87
+ ],
88
+ }
89
+ ],
90
+ temperature=0.7
91
+ )
92
+ result = response.choices[0].message.content.strip()
93
+ if "電話:+" in result:
94
+ phone_match = re.search(r'電話:\+(\d+)', result)
95
+ if phone_match and not phone_match.group(1).startswith('886'):
96
+ result = result.replace(
97
+ f"電話:+{phone_match.group(1)}",
98
+ f"電話:+{phone_match.group(1)}(不是來自臺灣的電話)"
99
+ )
100
+ url_match = re.search(r'連結:\s*(.+?)(?:\n|$)', result)
101
+ if url_match:
102
+ url = url_match.group(1).strip()
103
+ original_url = url
104
+ if url.lower() != "無":
105
+ try:
106
+ if "短網址:是" in result:
107
+ headers = {
108
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
109
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
110
+ 'sec-ch-ua-mobile': '?0',
111
+ 'sec-ch-ua-platform': '"Windows"',
112
+ 'sec-fetch-dest': 'document',
113
+ 'sec-fetch-mode': 'navigate',
114
+ 'sec-fetch-site': 'none',
115
+ 'Sec-Fetch-User': '?1',
116
+ 'sec-gpc': '1',
117
+ 'Cache-Control': 'no-cache',
118
+ 'Pragma': 'no-cache',
119
+ 'dnt': '1',
120
+ 'accept-language': 'zh-TW'
121
+ }
122
+ api_response = requests.get(f"https://sitecheck.sucuri.net/api/v3/?scan={url}", headers=headers)
123
+ api_data = api_response.json()
124
+ if "site" in api_data and "final_url" in api_data["site"]:
125
+ final_url = api_data["site"]["final_url"]
126
+ url = final_url # 使用解析後的真實網址
127
+ result = result.replace(
128
+ f"連結:{url_match.group(1).strip()}",
129
+ f"連結:{url_match.group(1).strip()}\n## 真實網址:{final_url}"
130
+ )
131
+ vt_headers = {
132
+ "accept": "application/json",
133
+ "x-apikey": os.getenv("YOUR_VirusTotal_API_KEY"),
134
+ "content-type": "application/x-www-form-urlencoded"
135
+ }
136
+ scan_data = {
137
+ "url": original_url
138
+ }
139
+ scan_response = requests.post(
140
+ "https://www.virustotal.com/api/v3/urls",
141
+ headers=vt_headers,
142
+ data=scan_data
143
+ )
144
+ scan_result = scan_response.json()
145
+ if "data" in scan_result and "id" in scan_result["data"]:
146
+ analysis_id = scan_result["data"]["id"]
147
+ url_id = analysis_id.split('-')[1]
148
+ vt_report_url = f"https://www.virustotal.com/gui/url/{url_id}"
149
+ result += f"\n## VirusTotal 分析報告:{vt_report_url}"
150
+ except Exception as e:
151
+ print(f"取得短網址資訊時發生錯誤:{e}")
152
+ gr.Warning(f"取得短網址資訊時發生錯誤:{e}")
153
+ process_time = time.time() - start_time
154
+ print(f"詐騙訊息分析完成,處理時間:{process_time:.2f} 秒")
155
+ gr.Info(f"詐騙訊息分析完成,處理時間:{process_time:.2f} 秒")
156
+ return {
157
+ result_header: gr.update(visible=True),
158
+ scam_result: gr.update(value=result, visible=True)
159
+ }
160
+ except Exception as e:
161
+ print(f"詐騙訊息分析時發生錯誤:{e}")
162
+ gr.Warning(f"詐騙訊息分析時發生錯誤:{e}")
163
+ return {
164
+ result_header: gr.update(visible=False),
165
+ scam_result: gr.update(value=f"Error:{e}", visible=True)
166
+ }
167
+
168
+ def clear_inputs():
169
+ return {
170
+ image_input: None,
171
+ result_header: gr.update(visible=False),
172
+ scam_result: gr.update(value="", visible=False)
173
+ }
174
+
175
+ custom_css = """
176
+ .center-aligned {
177
+ text-align: center !important;
178
+ color: #ff4081;
179
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
180
+ margin-bottom: 0 !important;
181
+ }
182
+ .gr-input, .gr-box, .gr-dropdown {
183
+ border-radius: 10px !important;
184
+ border: 2px solid #ff4081 !important;
185
+ margin: 0 !important;
186
+ }
187
+ .gr-input:focus, .gr-box:focus, .gr-dropdown:focus {
188
+ border-color: #f50057 !important;
189
+ box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
190
+ }
191
+ .file-background {
192
+ background-color: #B7E0FF !important;
193
+ padding: 15px !important;
194
+ border-radius: 10px !important;
195
+ margin: 0 !important;
196
+ height: auto;
197
+ }
198
+ .gen-button {
199
+ border-radius: 10px !important;
200
+ border: none !important;
201
+ background-color: #ff4081 !important;
202
+ color: white !important;
203
+ font-weight: bold !important;
204
+ transition: all 0.3s ease !important;
205
+ }
206
+ .gen-button:hover {
207
+ background-color: #f50057 !important;
208
+ transform: scale(1.05);
209
+ }
210
+ .api-background {
211
+ background-color: #FFCFB3 !important;
212
+ padding: 15px !important;
213
+ border-radius: 10px !important;
214
+ }
215
+ .text-background {
216
+ font-size: 20px !important;
217
+ padding: 5px !important;
218
+ border-radius: 10px !important;
219
+ border: 2px solid #B7E0FF !important;
220
+ margin: 0 !important;
221
+ text-align: left;
222
+ hyphens: auto;
223
+ word-wrap: break-word;
224
+ overflow-wrap: break-word;
225
+ }
226
+ .clear-button {
227
+ border-radius: 10px !important;
228
+ border: none !important;
229
+ background-color: #333333 !important;
230
+ color: white !important;
231
+ font-weight: bold !important;
232
+ transition: all 0.3s ease !important;
233
+ }
234
+ .clear-button:hover {
235
+ background-color: #000000 !important;
236
+ transform: scale(1.05);
237
+ }
238
+ .result-header {
239
+ font-size: 24px !important;
240
+ background-color: #FEF9D9 !important;
241
+ padding: 5px !important;
242
+ border-radius: 10px !important;
243
+ text-align: center !important;
244
+ display: block !important;
245
+ margin-bottom: 0px !important;
246
+ }
247
+ """
248
+
249
+ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as demo:
250
+ gr.Markdown("""
251
+ # 詐騙訊息分析 - 財政部財政資訊中心
252
+ > ### **※ 詐騙以虛假承諾及絢麗圖片作為引誘入套的詭計,務必保持警覺,使用 Frontier-Class Multimodal LLMs 進行訊息分析效果更佳。系統部署:江信宗,LLM:GPT-4o-mini。**
253
+ """, elem_classes="center-aligned")
254
+ with gr.Row():
255
+ with gr.Column():
256
+ image_input = gr.Image(
257
+ label="上傳圖片",
258
+ type="pil",
259
+ elem_classes="file-background",
260
+ image_mode="RGB",
261
+ sources=["upload"],
262
+ height="auto"
263
+ )
264
+ with gr.Column():
265
+ api_key = gr.Textbox(
266
+ label="API Key",
267
+ placeholder="輸入您的 API Key...",
268
+ type="password",
269
+ elem_classes="api-background"
270
+ )
271
+ clear_button = gr.Button("清除", elem_classes="clear-button")
272
+ analyze_btn = gr.Button("進行訊息分析", elem_classes="gen-button")
273
+ result_header = gr.HTML("""<div class="result-header">※ 訊息分析結果 ※</div>""", visible=False)
274
+ scam_result = gr.Markdown(label="詐騙分析結果", elem_classes="text-background", visible=False)
275
+ analyze_btn.click(
276
+ scam,
277
+ inputs=[image_input, api_key],
278
+ outputs=[result_header, scam_result]
279
+ )
280
+ clear_button.click(
281
+ fn=clear_inputs,
282
+ inputs=[],
283
+ outputs=[image_input, result_header, scam_result]
284
+ )
285
+
286
+ if __name__ == "__main__":
287
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ openai
3
+ Pillow
4
+ resend
5
+ requests