tester1hf commited on
Commit
14e2bbe
·
verified ·
1 Parent(s): d4f5ca8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +725 -190
app.py CHANGED
@@ -1,207 +1,742 @@
1
  import streamlit as st
2
  import requests
 
 
 
3
  import random
4
  import string
5
- import secrets
 
6
 
7
- # API Helper Functions
8
- def fetch_domains(base_url):
9
- """Fetch available domains from the service."""
10
- try:
11
- response = requests.get(f"{base_url}/domains")
12
- response.raise_for_status()
13
- return response.json()["hydra:member"]
14
- except requests.exceptions.RequestException as e:
15
- st.error(f"Failed to fetch domains: {e}")
16
- return []
17
 
18
- def create_account(base_url, address, password):
19
- """Create a new account."""
20
- payload = {"address": address, "password": password}
21
- response = requests.post(f"{base_url}/accounts", json=payload)
22
- response.raise_for_status()
23
- return response.json()
24
-
25
- def get_token(base_url, address, password):
26
- """Retrieve authentication token."""
27
- payload = {"address": address, "password": password}
28
- response = requests.post(f"{base_url}/token", json=payload)
29
- response.raise_for_status()
30
- return response.json()["token"]
31
-
32
- def fetch_messages(base_url, token):
33
- """Fetch all messages for the account."""
34
- headers = {"Authorization": f"Bearer {token}"}
35
- response = requests.get(f"{base_url}/messages", headers=headers)
36
- response.raise_for_status()
37
- return response.json()["hydra:member"]
38
-
39
- def fetch_full_message(base_url, message_id, token):
40
- """Fetch detailed message content."""
41
- headers = {"Authorization": f"Bearer {token}"}
42
- response = requests.get(f"{base_url}/messages/{message_id}", headers=headers)
43
- response.raise_for_status()
44
- return response.json()
45
-
46
- def mark_message_as_read(base_url, message_id, token):
47
- """Mark a message as read."""
48
- headers = {"Authorization": f"Bearer {token}"}
49
- response = requests.patch(f"{base_url}/messages/{message_id}", headers=headers)
50
- response.raise_for_status()
51
-
52
- def delete_message(base_url, message_id, token):
53
- """Delete a message."""
54
- headers = {"Authorization": f"Bearer {token}"}
55
- response = requests.delete(f"{base_url}/messages/{message_id}", headers=headers)
56
- response.raise_for_status()
57
-
58
- def delete_account(base_url, account_id, token):
59
- """Delete the account."""
60
- headers = {"Authorization": f"Bearer {token}"}
61
- response = requests.delete(f"{base_url}/accounts/{account_id}", headers=headers)
62
- response.raise_for_status()
63
-
64
- def fetch_attachment(base_url, download_url, token):
65
- """Fetch attachment data."""
66
- headers = {"Authorization": f"Bearer {token}"}
67
- response = requests.get(f"{base_url}{download_url}", headers=headers)
68
- response.raise_for_status()
69
- return response.content
70
-
71
- # Initialize Session State
72
- if "account" not in st.session_state:
73
- st.session_state.account = None
74
- if "token" not in st.session_state:
75
- st.session_state.token = None
76
- if "messages" not in st.session_state:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  st.session_state.messages = []
78
- if "selected_message" not in st.session_state:
79
  st.session_state.selected_message = None
80
- if "attachment_data" not in st.session_state:
81
- st.session_state.attachment_data = {}
82
- if "username" not in st.session_state:
83
- st.session_state.username = ""
84
-
85
- # Sidebar: Service Selection and Account Management
86
- st.sidebar.title("Temporary Mail Client")
87
-
88
- service = st.sidebar.selectbox("Select Service", ["Mail.tm", "Mail.gw"])
89
- base_url = "https://api.mail.tm" if service == "Mail.tm" else "https://api.mail.gw"
90
-
91
- if st.session_state.account is None:
92
- st.sidebar.subheader("Create Account")
93
- domains = fetch_domains(base_url)
94
- domain_options = [d["domain"] for d in domains] if domains else []
95
- selected_domain = st.sidebar.selectbox("Select Domain", domain_options)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- # Username input with random generation option
98
- if st.sidebar.button("Generate Random Username"):
99
- st.session_state.username = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
100
- username = st.sidebar.text_input("Username", value=st.session_state.username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- if st.sidebar.button("Create Account"):
103
- if username and selected_domain:
104
- address = f"{username}@{selected_domain}"
105
- password = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(12))
106
- try:
107
- account = create_account(base_url, address, password)
108
- token = get_token(base_url, address, password)
109
- st.session_state.account = account
110
- st.session_state.token = token
111
- st.session_state.username = "" # Reset username input
112
- st.sidebar.success(f"Account created: {address}")
113
- except requests.exceptions.RequestException as e:
114
- st.sidebar.error(f"Failed to create account: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  else:
116
- st.sidebar.error("Please provide a username and select a domain.")
117
- else:
118
- st.sidebar.subheader("Current Account")
119
- st.sidebar.write(st.session_state.account["address"])
120
- if st.sidebar.button("Delete Account"):
121
- try:
122
- delete_account(base_url, st.session_state.account["id"], st.session_state.token)
123
- st.session_state.account = None
124
- st.session_state.token = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  st.session_state.messages = []
126
  st.session_state.selected_message = None
127
- st.session_state.attachment_data = {}
128
- st.sidebar.success("Account deleted.")
129
- except requests.exceptions.RequestException as e:
130
- st.sidebar.error(f"Failed to delete account: {e}")
131
-
132
- # Main Area: Inbox and Message View
133
- if st.session_state.account:
134
- st.title(f"Inbox - {st.session_state.account['address']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- # Refresh Button
137
- if st.button("Refresh Inbox"):
138
- try:
139
- st.session_state.messages = fetch_messages(base_url, st.session_state.token)
140
- st.success("Inbox refreshed.")
141
- except requests.exceptions.RequestException as e:
142
- st.error(f"Failed to fetch messages: {e}")
143
 
144
- # Message List
145
- if st.session_state.messages:
146
- st.subheader("Messages")
147
- for msg in st.session_state.messages:
148
- col1, col2 = st.columns([4, 1])
149
- with col1:
150
- label = f"{'[Unread] ' if not msg['seen'] else ''}{msg['from']['address']} - {msg['subject']}"
151
- if st.button(label, key=f"select_{msg['id']}"):
152
- st.session_state.selected_message = msg
153
- if not msg["seen"]:
154
- try:
155
- mark_message_as_read(base_url, msg["id"], st.session_state.token)
156
- msg["seen"] = True
157
- except requests.exceptions.RequestException as e:
158
- st.error(f"Failed to mark message as read: {e}")
159
- with col2:
160
- if st.button("Delete", key=f"delete_{msg['id']}"):
161
- try:
162
- delete_message(base_url, msg["id"], st.session_state.token)
163
- st.session_state.messages = [m for m in st.session_state.messages if m["id"] != msg["id"]]
164
- if st.session_state.selected_message and st.session_state.selected_message["id"] == msg["id"]:
165
- st.session_state.selected_message = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  st.experimental_rerun()
167
- except requests.exceptions.RequestException as e:
168
- st.error(f"Failed to delete message: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  else:
170
- st.write("No messages yet.")
171
-
172
- # Selected Message View
173
- if st.session_state.selected_message:
174
- st.subheader("Message Content")
175
- try:
176
- full_message = fetch_full_message(base_url, st.session_state.selected_message["id"], st.session_state.token)
177
- st.write(f"**From:** {full_message['from']['address']}")
178
- st.write(f"**Subject:** {full_message['subject']}")
179
- st.write(f"**Date:** {full_message['createdAt']}")
180
- st.write("**Body:**")
181
- st.write(full_message["text"] if "text" in full_message else "No plain text available.")
182
-
183
- # Attachments
184
- if full_message["hasAttachments"] and "attachments" in full_message:
185
- st.subheader("Attachments")
186
- for attachment in full_message["attachments"]:
187
- if st.button(f"Prepare Download: {attachment['filename']}", key=f"prep_{attachment['id']}"):
188
- try:
189
- attachment_data = fetch_attachment(base_url, attachment["downloadUrl"], st.session_state.token)
190
- st.session_state.attachment_data[attachment["id"]] = attachment_data
191
- except requests.exceptions.RequestException as e:
192
- st.error(f"Failed to fetch attachment: {e}")
193
- if attachment["id"] in st.session_state.attachment_data:
194
- st.download_button(
195
- label=f"Download {attachment['filename']}",
196
- data=st.session_state.attachment_data[attachment["id"]],
197
- file_name=attachment["filename"],
198
- mime=attachment["contentType"],
199
- key=f"down_{attachment['id']}"
200
- )
201
- except requests.exceptions.RequestException as e:
202
- st.error(f"Failed to fetch message details: {e}")
203
- else:
204
- st.write("Please create an account to view your inbox.")
205
-
206
- # Footer
207
- st.sidebar.markdown("Powered by [Mail.tm](https://mail.tm) and [Mail.gw](https://mail.gw)")
 
1
  import streamlit as st
2
  import requests
3
+ import json
4
+ import time
5
+ import re
6
  import random
7
  import string
8
+ from datetime import datetime
9
+ import html
10
 
11
+ # Set page configuration
12
+ st.set_page_config(
13
+ page_title="Temp Mail Client",
14
+ page_icon="✉️",
15
+ layout="wide",
16
+ initial_sidebar_state="expanded"
17
+ )
 
 
 
18
 
19
+ # Custom CSS for styling
20
+ st.markdown("""
21
+ <style>
22
+ .main {
23
+ background-color: #f9f9f9;
24
+ }
25
+ .email-list {
26
+ border: 1px solid #ddd;
27
+ border-radius: 5px;
28
+ padding: 10px;
29
+ margin-bottom: 10px;
30
+ cursor: pointer;
31
+ transition: background-color 0.3s;
32
+ }
33
+ .email-list:hover {
34
+ background-color: #f0f0f0;
35
+ }
36
+ .email-selected {
37
+ background-color: #e6f7ff;
38
+ border-left: 3px solid #1890ff;
39
+ }
40
+ .email-header {
41
+ font-weight: bold;
42
+ margin-bottom: 5px;
43
+ }
44
+ .email-subject {
45
+ font-size: 16px;
46
+ white-space: nowrap;
47
+ overflow: hidden;
48
+ text-overflow: ellipsis;
49
+ }
50
+ .email-from {
51
+ font-size: 14px;
52
+ color: #666;
53
+ }
54
+ .email-time {
55
+ font-size: 12px;
56
+ color: #999;
57
+ text-align: right;
58
+ }
59
+ .email-preview {
60
+ font-size: 14px;
61
+ color: #666;
62
+ white-space: nowrap;
63
+ overflow: hidden;
64
+ text-overflow: ellipsis;
65
+ }
66
+ .unread {
67
+ font-weight: bold;
68
+ }
69
+ .email-content {
70
+ border: 1px solid #ddd;
71
+ border-radius: 5px;
72
+ padding: 20px;
73
+ background-color: white;
74
+ }
75
+ .email-actions {
76
+ display: flex;
77
+ justify-content: flex-end;
78
+ margin-bottom: 10px;
79
+ }
80
+ .stButton button {
81
+ padding: 2px 10px;
82
+ font-size: 14px;
83
+ }
84
+ .attachment {
85
+ border: 1px solid #ddd;
86
+ border-radius: 5px;
87
+ padding: 10px;
88
+ margin: 5px 0;
89
+ display: flex;
90
+ align-items: center;
91
+ background-color: #f5f5f5;
92
+ }
93
+ .attachment-icon {
94
+ margin-right: 10px;
95
+ color: #1890ff;
96
+ }
97
+ .attachment-name {
98
+ flex-grow: 1;
99
+ }
100
+ .attachment-size {
101
+ color: #999;
102
+ margin-right: 10px;
103
+ }
104
+ .sidebar-content {
105
+ padding: 10px;
106
+ }
107
+ .provider-select {
108
+ margin-bottom: 20px;
109
+ }
110
+ .refresh-button {
111
+ width: 100%;
112
+ margin-bottom: 10px;
113
+ }
114
+ .copy-button {
115
+ width: 100%;
116
+ }
117
+ .header-container {
118
+ display: flex;
119
+ justify-content: space-between;
120
+ align-items: center;
121
+ margin-bottom: 20px;
122
+ }
123
+ .app-header {
124
+ font-size: 24px;
125
+ font-weight: bold;
126
+ color: #1890ff;
127
+ }
128
+ .loading-spinner {
129
+ display: flex;
130
+ justify-content: center;
131
+ align-items: center;
132
+ height: 100px;
133
+ }
134
+ </style>
135
+ """, unsafe_allow_html=True)
136
+
137
+ # Initialize session state variables
138
+ if 'provider' not in st.session_state:
139
+ st.session_state.provider = "mail.tm"
140
+ if 'domains' not in st.session_state:
141
+ st.session_state.domains = []
142
+ if 'email' not in st.session_state:
143
+ st.session_state.email = ""
144
+ if 'password' not in st.session_state:
145
+ st.session_state.password = ""
146
+ if 'token' not in st.session_state:
147
+ st.session_state.token = ""
148
+ if 'account_id' not in st.session_state:
149
+ st.session_state.account_id = ""
150
+ if 'messages' not in st.session_state:
151
  st.session_state.messages = []
152
+ if 'selected_message' not in st.session_state:
153
  st.session_state.selected_message = None
154
+ if 'message_content' not in st.session_state:
155
+ st.session_state.message_content = None
156
+ if 'last_refresh' not in st.session_state:
157
+ st.session_state.last_refresh = datetime.now()
158
+
159
+ # API configuration
160
+ API_ENDPOINTS = {
161
+ "mail.tm": {
162
+ "base_url": "https://api.mail.tm",
163
+ "mercure_url": "https://mercure.mail.tm/.well-known/mercure"
164
+ },
165
+ "mail.gw": {
166
+ "base_url": "https://api.mail.gw",
167
+ "mercure_url": "https://api.mail.gw/.well-known/mercure"
168
+ }
169
+ }
170
+
171
+ def get_base_url():
172
+ return API_ENDPOINTS[st.session_state.provider]["base_url"]
173
+
174
+ def get_mercure_url():
175
+ return API_ENDPOINTS[st.session_state.provider]["mercure_url"]
176
+
177
+ def get_domains():
178
+ """Fetch available domains from the API"""
179
+ try:
180
+ response = requests.get(f"{get_base_url()}/domains")
181
+ if response.status_code == 200:
182
+ data = response.json()
183
+ st.session_state.domains = [domain["domain"] for domain in data["hydra:member"]]
184
+ return st.session_state.domains
185
+ else:
186
+ st.error(f"Failed to fetch domains: {response.status_code}")
187
+ return []
188
+ except Exception as e:
189
+ st.error(f"Error fetching domains: {str(e)}")
190
+ return []
191
+
192
+ def create_account(email, password):
193
+ """Create a new email account"""
194
+ try:
195
+ payload = {
196
+ "address": email,
197
+ "password": password
198
+ }
199
+ response = requests.post(
200
+ f"{get_base_url()}/accounts",
201
+ json=payload
202
+ )
203
+
204
+ if response.status_code == 201:
205
+ data = response.json()
206
+ st.session_state.account_id = data["id"]
207
+ return True
208
+ else:
209
+ st.error(f"Failed to create account: {response.status_code} - {response.text}")
210
+ return False
211
+ except Exception as e:
212
+ st.error(f"Error creating account: {str(e)}")
213
+ return False
214
+
215
+ def get_token(email, password):
216
+ """Get authentication token"""
217
+ try:
218
+ payload = {
219
+ "address": email,
220
+ "password": password
221
+ }
222
+ response = requests.post(
223
+ f"{get_base_url()}/token",
224
+ json=payload
225
+ )
226
+
227
+ if response.status_code == 200:
228
+ data = response.json()
229
+ st.session_state.token = data["token"]
230
+ st.session_state.account_id = data["id"]
231
+ return True
232
+ else:
233
+ st.error(f"Failed to get token: {response.status_code} - {response.text}")
234
+ return False
235
+ except Exception as e:
236
+ st.error(f"Error getting token: {str(e)}")
237
+ return False
238
+
239
+ def get_account_info():
240
+ """Get account information"""
241
+ if not st.session_state.token:
242
+ return None
243
+
244
+ try:
245
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
246
+ response = requests.get(
247
+ f"{get_base_url()}/me",
248
+ headers=headers
249
+ )
250
+
251
+ if response.status_code == 200:
252
+ return response.json()
253
+ else:
254
+ st.error(f"Failed to get account info: {response.status_code}")
255
+ return None
256
+ except Exception as e:
257
+ st.error(f"Error getting account info: {str(e)}")
258
+ return None
259
+
260
+ def get_messages():
261
+ """Get messages for the current account"""
262
+ if not st.session_state.token:
263
+ return []
264
+
265
+ try:
266
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
267
+ response = requests.get(
268
+ f"{get_base_url()}/messages",
269
+ headers=headers
270
+ )
271
+
272
+ if response.status_code == 200:
273
+ data = response.json()
274
+ st.session_state.messages = data["hydra:member"]
275
+ return st.session_state.messages
276
+ else:
277
+ st.error(f"Failed to get messages: {response.status_code}")
278
+ return []
279
+ except Exception as e:
280
+ st.error(f"Error getting messages: {str(e)}")
281
+ return []
282
+
283
+ def get_message_content(message_id):
284
+ """Get detailed content of a specific message"""
285
+ if not st.session_state.token:
286
+ return None
287
+
288
+ try:
289
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
290
+ response = requests.get(
291
+ f"{get_base_url()}/messages/{message_id}",
292
+ headers=headers
293
+ )
294
+
295
+ if response.status_code == 200:
296
+ return response.json()
297
+ else:
298
+ st.error(f"Failed to get message content: {response.status_code}")
299
+ return None
300
+ except Exception as e:
301
+ st.error(f"Error getting message content: {str(e)}")
302
+ return None
303
+
304
+ def mark_as_read(message_id):
305
+ """Mark a message as read"""
306
+ if not st.session_state.token:
307
+ return False
308
+
309
+ try:
310
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
311
+ response = requests.patch(
312
+ f"{get_base_url()}/messages/{message_id}",
313
+ headers=headers
314
+ )
315
+
316
+ if response.status_code == 200:
317
+ # Update the message in the session state
318
+ for i, msg in enumerate(st.session_state.messages):
319
+ if msg["id"] == message_id:
320
+ st.session_state.messages[i]["seen"] = True
321
+ return True
322
+ else:
323
+ st.error(f"Failed to mark message as read: {response.status_code}")
324
+ return False
325
+ except Exception as e:
326
+ st.error(f"Error marking message as read: {str(e)}")
327
+ return False
328
+
329
+ def delete_message(message_id):
330
+ """Delete a message"""
331
+ if not st.session_state.token:
332
+ return False
333
 
334
+ try:
335
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
336
+ response = requests.delete(
337
+ f"{get_base_url()}/messages/{message_id}",
338
+ headers=headers
339
+ )
340
+
341
+ if response.status_code == 204:
342
+ # Remove the message from the session state
343
+ st.session_state.messages = [msg for msg in st.session_state.messages if msg["id"] != message_id]
344
+ if st.session_state.selected_message == message_id:
345
+ st.session_state.selected_message = None
346
+ st.session_state.message_content = None
347
+ return True
348
+ else:
349
+ st.error(f"Failed to delete message: {response.status_code}")
350
+ return False
351
+ except Exception as e:
352
+ st.error(f"Error deleting message: {str(e)}")
353
+ return False
354
+
355
+ def delete_account():
356
+ """Delete the current account"""
357
+ if not st.session_state.token or not st.session_state.account_id:
358
+ return False
359
 
360
+ try:
361
+ headers = {"Authorization": f"Bearer {st.session_state.token}"}
362
+ response = requests.delete(
363
+ f"{get_base_url()}/accounts/{st.session_state.account_id}",
364
+ headers=headers
365
+ )
366
+
367
+ if response.status_code == 204:
368
+ # Clear session state
369
+ st.session_state.email = ""
370
+ st.session_state.password = ""
371
+ st.session_state.token = ""
372
+ st.session_state.account_id = ""
373
+ st.session_state.messages = []
374
+ st.session_state.selected_message = None
375
+ st.session_state.message_content = None
376
+ return True
377
+ else:
378
+ st.error(f"Failed to delete account: {response.status_code}")
379
+ return False
380
+ except Exception as e:
381
+ st.error(f"Error deleting account: {str(e)}")
382
+ return False
383
+
384
+ def format_date(date_str):
385
+ """Format date string to a more readable format"""
386
+ try:
387
+ dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
388
+ now = datetime.now()
389
+
390
+ # If today, show only time
391
+ if dt.date() == now.date():
392
+ return dt.strftime("%H:%M")
393
+ # If this year, show month and day
394
+ elif dt.year == now.year:
395
+ return dt.strftime("%b %d")
396
+ # Otherwise show full date
397
  else:
398
+ return dt.strftime("%Y-%m-%d")
399
+ except:
400
+ return date_str
401
+
402
+ def generate_random_username(length=10):
403
+ """Generate a random username for email"""
404
+ letters = string.ascii_lowercase + string.digits
405
+ return ''.join(random.choice(letters) for i in range(length))
406
+
407
+ def format_size(size_bytes):
408
+ """Format file size in bytes to human-readable format"""
409
+ if size_bytes < 1024:
410
+ return f"{size_bytes} B"
411
+ elif size_bytes < 1024 * 1024:
412
+ return f"{size_bytes/1024:.1f} KB"
413
+ else:
414
+ return f"{size_bytes/(1024*1024):.1f} MB"
415
+
416
+ def sanitize_html(html_content):
417
+ """Basic sanitization of HTML content"""
418
+ # Remove potentially dangerous tags and attributes
419
+ # This is a very basic implementation - in production, use a proper HTML sanitizer
420
+ dangerous_tags = ['script', 'iframe', 'object', 'embed']
421
+ for tag in dangerous_tags:
422
+ html_content = re.sub(f'<{tag}.*?</{tag}>', '', html_content, flags=re.DOTALL)
423
+ html_content = re.sub(f'<{tag}.*?>', '', html_content, flags=re.DOTALL)
424
+
425
+ # Remove on* attributes
426
+ html_content = re.sub(r'on\w+=".*?"', '', html_content)
427
+ html_content = re.sub(r"on\w+='.*?'", '', html_content)
428
+
429
+ return html_content
430
+
431
+ # Main application layout
432
+ def main():
433
+ # Sidebar for account management
434
+ with st.sidebar:
435
+ st.markdown("<div class='sidebar-content'>", unsafe_allow_html=True)
436
+
437
+ # Provider selection
438
+ st.markdown("<div class='provider-select'>", unsafe_allow_html=True)
439
+ provider = st.selectbox(
440
+ "Select Provider",
441
+ options=["mail.tm", "mail.gw"],
442
+ index=0 if st.session_state.provider == "mail.tm" else 1,
443
+ key="provider_select"
444
+ )
445
+ st.markdown("</div>", unsafe_allow_html=True)
446
+
447
+ if provider != st.session_state.provider:
448
+ st.session_state.provider = provider
449
+ st.session_state.domains = []
450
+ st.session_state.email = ""
451
+ st.session_state.password = ""
452
+ st.session_state.token = ""
453
+ st.session_state.account_id = ""
454
  st.session_state.messages = []
455
  st.session_state.selected_message = None
456
+ st.session_state.message_content = None
457
+ get_domains()
458
+
459
+ # Account creation/login section
460
+ if not st.session_state.token:
461
+ st.subheader("Create Email Account")
462
+
463
+ if not st.session_state.domains:
464
+ get_domains()
465
+
466
+ if st.session_state.domains:
467
+ username = st.text_input("Username (optional)",
468
+ value=generate_random_username(),
469
+ help="Leave blank to generate random username")
470
+
471
+ domain = st.selectbox("Domain", options=st.session_state.domains)
472
+
473
+ if not username:
474
+ username = generate_random_username()
475
+
476
+ email = f"{username}@{domain}"
477
+ st.session_state.email = email
478
+
479
+ password = st.text_input("Password",
480
+ value=''.join(random.choices(string.ascii_letters + string.digits, k=12)),
481
+ type="password")
482
+ st.session_state.password = password
483
+
484
+ col1, col2 = st.columns(2)
485
+ with col1:
486
+ if st.button("Create Account", use_container_width=True):
487
+ with st.spinner("Creating account..."):
488
+ if create_account(email, password):
489
+ if get_token(email, password):
490
+ st.success("Account created successfully!")
491
+ st.experimental_rerun()
492
+
493
+ with col2:
494
+ if st.button("Login", use_container_width=True):
495
+ with st.spinner("Logging in..."):
496
+ if get_token(email, password):
497
+ st.success("Logged in successfully!")
498
+ st.experimental_rerun()
499
+ else:
500
+ st.warning("Unable to fetch domains. Please try again later.")
501
+
502
+ # Account info and management
503
+ else:
504
+ account_info = get_account_info()
505
+ if account_info:
506
+ st.subheader("Account Information")
507
+ st.markdown(f"**Email:** {account_info['address']}")
508
+
509
+ # Copy email button
510
+ if st.button("Copy Email", key="copy_email", use_container_width=True):
511
+ st.write("Email copied to clipboard!")
512
+ # Note: This doesn't actually copy to clipboard in Streamlit,
513
+ # but we're simulating the UI behavior
514
+
515
+ # Display quota information
516
+ quota_used = account_info.get('used', 0)
517
+ quota_total = account_info.get('quota', 0)
518
+ if quota_total > 0:
519
+ quota_percentage = (quota_used / quota_total) * 100
520
+ st.progress(quota_percentage / 100)
521
+ st.markdown(f"Storage: {quota_used}/{quota_total} ({quota_percentage:.1f}%)")
522
+
523
+ # Refresh button
524
+ if st.button("Refresh Inbox", key="refresh_inbox", use_container_width=True):
525
+ with st.spinner("Refreshing..."):
526
+ get_messages()
527
+ st.session_state.last_refresh = datetime.now()
528
+ st.experimental_rerun()
529
+
530
+ # Logout and delete account buttons
531
+ col1, col2 = st.columns(2)
532
+ with col1:
533
+ if st.button("Logout", use_container_width=True):
534
+ st.session_state.token = ""
535
+ st.session_state.account_id = ""
536
+ st.session_state.messages = []
537
+ st.session_state.selected_message = None
538
+ st.session_state.message_content = None
539
+ st.experimental_rerun()
540
+
541
+ with col2:
542
+ if st.button("Delete Account", use_container_width=True):
543
+ if delete_account():
544
+ st.success("Account deleted successfully!")
545
+ st.experimental_rerun()
546
+
547
+ st.markdown("</div>", unsafe_allow_html=True)
548
 
549
+ # Main content area
550
+ st.markdown("<div class='header-container'>", unsafe_allow_html=True)
551
+ st.markdown("<div class='app-header'>✉️ Temp Mail Client</div>", unsafe_allow_html=True)
552
+ st.markdown(f"<div>Last refreshed: {st.session_state.last_refresh.strftime('%H:%M:%S')}</div>", unsafe_allow_html=True)
553
+ st.markdown("</div>", unsafe_allow_html=True)
 
 
554
 
555
+ # If logged in, show messages
556
+ if st.session_state.token:
557
+ # Get messages if not already loaded
558
+ if not st.session_state.messages:
559
+ with st.spinner("Loading messages..."):
560
+ get_messages()
561
+
562
+ # Split the screen into two columns for message list and content
563
+ col1, col2 = st.columns([1, 2])
564
+
565
+ with col1:
566
+ st.subheader("Inbox")
567
+
568
+ # Display message list
569
+ if st.session_state.messages:
570
+ for message in st.session_state.messages:
571
+ # Determine if message is selected
572
+ is_selected = st.session_state.selected_message == message["id"]
573
+ # Determine if message is unread
574
+ is_unread = not message.get("seen", False)
575
+
576
+ # Create a clickable message container
577
+ message_class = "email-list"
578
+ if is_selected:
579
+ message_class += " email-selected"
580
+
581
+ st.markdown(f"<div class='{message_class}' onclick='null' id='message-{message['id']}'>", unsafe_allow_html=True)
582
+
583
+ # Message header (from and time)
584
+ st.markdown("<div class='email-header'>", unsafe_allow_html=True)
585
+
586
+ # From name/address
587
+ from_name = message.get("from", {}).get("name", "")
588
+ from_address = message.get("from", {}).get("address", "Unknown")
589
+ display_from = from_name if from_name else from_address
590
+
591
+ # Format date
592
+ date_str = format_date(message.get("createdAt", ""))
593
+
594
+ col_from, col_date = st.columns([3, 1])
595
+ with col_from:
596
+ st.markdown(f"<div class='email-from{' unread' if is_unread else ''}'>{display_from}</div>", unsafe_allow_html=True)
597
+ with col_date:
598
+ st.markdown(f"<div class='email-time'>{date_str}</div>", unsafe_allow_html=True)
599
+
600
+ st.markdown("</div>", unsafe_allow_html=True)
601
+
602
+ # Subject
603
+ subject = message.get("subject", "(No subject)")
604
+ st.markdown(f"<div class='email-subject{' unread' if is_unread else ''}'>{subject}</div>", unsafe_allow_html=True)
605
+
606
+ # Preview
607
+ preview = message.get("intro", "")
608
+ st.markdown(f"<div class='email-preview'>{preview}</div>", unsafe_allow_html=True)
609
+
610
+ # Close the message container
611
+ st.markdown("</div>", unsafe_allow_html=True)
612
+
613
+ # Handle click on message
614
+ if st.button("View", key=f"view_{message['id']}", help="View this message"):
615
+ st.session_state.selected_message = message["id"]
616
+ st.session_state.message_content = get_message_content(message["id"])
617
+ if not message.get("seen", False):
618
+ mark_as_read(message["id"])
619
+ st.experimental_rerun()
620
+ else:
621
+ st.info("No messages yet. Emails sent to your address will appear here.")
622
+
623
+ with col2:
624
+ # Display message content if a message is selected
625
+ if st.session_state.selected_message and st.session_state.message_content:
626
+ message = st.session_state.message_content
627
+
628
+ # Message actions
629
+ st.markdown("<div class='email-actions'>", unsafe_allow_html=True)
630
+
631
+ col1, col2 = st.columns([1, 1])
632
+ with col1:
633
+ if st.button("Back to Inbox", key="back_to_inbox"):
634
+ st.session_state.selected_message = None
635
+ st.session_state.message_content = None
636
  st.experimental_rerun()
637
+
638
+ with col2:
639
+ if st.button("Delete", key="delete_message"):
640
+ if delete_message(st.session_state.selected_message):
641
+ st.success("Message deleted!")
642
+ st.session_state.selected_message = None
643
+ st.session_state.message_content = None
644
+ st.experimental_rerun()
645
+
646
+ st.markdown("</div>", unsafe_allow_html=True)
647
+
648
+ # Message content container
649
+ st.markdown("<div class='email-content'>", unsafe_allow_html=True)
650
+
651
+ # Subject
652
+ subject = message.get("subject", "(No subject)")
653
+ st.markdown(f"<h2>{subject}</h2>", unsafe_allow_html=True)
654
+
655
+ # From
656
+ from_name = message.get("from", {}).get("name", "")
657
+ from_address = message.get("from", {}).get("address", "Unknown")
658
+ display_from = f"{from_name} <{from_address}>" if from_name else from_address
659
+ st.markdown(f"<p><strong>From:</strong> {display_from}</p>", unsafe_allow_html=True)
660
+
661
+ # To
662
+ to_addresses = []
663
+ for to in message.get("to", []):
664
+ name = to.get("name", "")
665
+ address = to.get("address", "")
666
+ if name:
667
+ to_addresses.append(f"{name} <{address}>")
668
+ else:
669
+ to_addresses.append(address)
670
+
671
+ st.markdown(f"<p><strong>To:</strong> {', '.join(to_addresses)}</p>", unsafe_allow_html=True)
672
+
673
+ # Date
674
+ date_str = message.get("createdAt", "")
675
+ if date_str:
676
+ try:
677
+ dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
678
+ formatted_date = dt.strftime("%a, %d %b %Y %H:%M:%S")
679
+ st.markdown(f"<p><strong>Date:</strong> {formatted_date}</p>", unsafe_allow_html=True)
680
+ except:
681
+ st.markdown(f"<p><strong>Date:</strong> {date_str}</p>", unsafe_allow_html=True)
682
+
683
+ # Attachments
684
+ attachments = message.get("attachments", [])
685
+ if attachments:
686
+ st.markdown("<div><strong>Attachments:</strong></div>", unsafe_allow_html=True)
687
+ for attachment in attachments:
688
+ filename = attachment.get("filename", "Unknown")
689
+ size = attachment.get("size", 0)
690
+ content_type = attachment.get("contentType", "application/octet-stream")
691
+ download_url = attachment.get("downloadUrl", "")
692
+
693
+ st.markdown(f"""
694
+ <div class='attachment'>
695
+ <div class='attachment-icon'>📎</div>
696
+ <div class='attachment-name'>{filename}</div>
697
+ <div class='attachment-size'>{format_size(size)}</div>
698
+ </div>
699
+ """, unsafe_allow_html=True)
700
+
701
+ # Message body
702
+ st.markdown("<hr>", unsafe_allow_html=True)
703
+
704
+ # Try to use HTML content first, fall back to text
705
+ html_content = message.get("html", [""])[0] if message.get("html") else ""
706
+ text_content = message.get("text", "")
707
+
708
+ if html_content:
709
+ # Sanitize HTML content
710
+ safe_html = sanitize_html(html_content)
711
+ st.markdown(f"<div>{safe_html}</div>", unsafe_allow_html=True)
712
+ elif text_content:
713
+ # Display plain text with line breaks preserved
714
+ st.markdown(f"<pre style='white-space: pre-wrap;'>{html.escape(text_content)}</pre>", unsafe_allow_html=True)
715
+ else:
716
+ st.markdown("<p>(No content)</p>", unsafe_allow_html=True)
717
+
718
+ st.markdown("</div>", unsafe_allow_html=True)
719
+ else:
720
+ st.info("Select a message from the inbox to view its content.")
721
  else:
722
+ # If not logged in, show welcome message
723
+ st.markdown("""
724
+ <div style="text-align: center; padding: 50px 20px;">
725
+ <h1>Welcome to Temp Mail Client</h1>
726
+ <p style="font-size: 18px; margin: 20px 0;">
727
+ Create a temporary email address to protect your privacy.
728
+ </p>
729
+ <div style="max-width: 600px; margin: 0 auto; text-align: left; background-color: #f5f5f5; padding: 20px; border-radius: 10px;">
730
+ <h3>How it works:</h3>
731
+ <ol style="font-size: 16px;">
732
+ <li>Create a temporary email account using the sidebar</li>
733
+ <li>Use this email address for sign-ups or verification</li>
734
+ <li>Receive emails instantly in this interface</li>
735
+ <li>Delete the account when you're done</li>
736
+ </ol>
737
+ </div>
738
+ </div>
739
+ """, unsafe_allow_html=True)
740
+
741
+ if __name__ == "__main__":
742
+ main()