Taf2023 commited on
Commit
563eac9
·
verified ·
1 Parent(s): 6ddc09c

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1758 -0
app.py ADDED
@@ -0,0 +1,1758 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import threading
4
+ import time
5
+ from datetime import datetime, timezone
6
+ from flask import Flask, request, jsonify, render_template_string
7
+ from flask_cors import CORS
8
+ from werkzeug.serving import run_simple
9
+ import logging
10
+
11
+ # Configure logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ app = Flask(__name__)
16
+ app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'
17
+
18
+ # Enable CORS for all routes
19
+ CORS(app)
20
+
21
+ # Enhanced data storage with chat functionality and better time handling
22
+ class EnhancedDataStore:
23
+ def __init__(self, backup_file='enhanced_data_backup.json'):
24
+ self.backup_file = backup_file
25
+ self.data = {
26
+ 'posts': [],
27
+ 'comments': [],
28
+ 'chat_messages': [],
29
+ 'next_post_id': 1,
30
+ 'next_comment_id': 1,
31
+ 'next_chat_id': 1,
32
+ 'app_version': '2.0.0',
33
+ 'last_updated': datetime.utcnow().isoformat()
34
+ }
35
+ self.lock = threading.Lock()
36
+ self.load_data()
37
+
38
+ # Start background backup thread
39
+ self.backup_thread = threading.Thread(target=self.periodic_backup, daemon=True)
40
+ self.backup_thread.start()
41
+
42
+ def get_utc_timestamp(self):
43
+ """Get current UTC timestamp"""
44
+ return datetime.utcnow().isoformat() + 'Z'
45
+
46
+ def load_data(self):
47
+ """Load data from backup file if it exists"""
48
+ try:
49
+ if os.path.exists(self.backup_file):
50
+ with open(self.backup_file, 'r', encoding='utf-8') as f:
51
+ loaded_data = json.load(f)
52
+
53
+ # Migrate old data structure if needed
54
+ if 'chat_messages' not in loaded_data:
55
+ loaded_data['chat_messages'] = []
56
+ loaded_data['next_chat_id'] = 1
57
+
58
+ if 'app_version' not in loaded_data:
59
+ loaded_data['app_version'] = '2.0.0'
60
+
61
+ self.data.update(loaded_data)
62
+ logger.info(f"Data loaded from {self.backup_file}: {len(self.data['posts'])} posts, {len(self.data['chat_messages'])} chat messages")
63
+ else:
64
+ logger.info("No backup file found, starting with empty data")
65
+ except Exception as e:
66
+ logger.error(f"Error loading data: {str(e)}")
67
+
68
+ def save_data(self):
69
+ """Save data to backup file"""
70
+ try:
71
+ with self.lock:
72
+ self.data['last_updated'] = self.get_utc_timestamp()
73
+ with open(self.backup_file, 'w', encoding='utf-8') as f:
74
+ json.dump(self.data, f, ensure_ascii=False, indent=2)
75
+ logger.info(f"Data saved to {self.backup_file}")
76
+ except Exception as e:
77
+ logger.error(f"Error saving data: {str(e)}")
78
+
79
+ def periodic_backup(self):
80
+ """Periodically backup data every 30 seconds"""
81
+ while True:
82
+ time.sleep(30)
83
+ self.save_data()
84
+
85
+ def add_post(self, name, note, youtube_link=''):
86
+ """Add a new post with UTC timestamp"""
87
+ try:
88
+ with self.lock:
89
+ post = {
90
+ 'id': self.data['next_post_id'],
91
+ 'name': str(name).strip(),
92
+ 'note': str(note).strip(),
93
+ 'youtube_link': str(youtube_link).strip(),
94
+ 'likes': 0,
95
+ 'created_at': self.get_utc_timestamp()
96
+ }
97
+ self.data['posts'].append(post)
98
+ self.data['next_post_id'] += 1
99
+ self.save_data()
100
+ logger.info(f"Post added: ID {post['id']}, Name: {post['name']}")
101
+ return post
102
+ except Exception as e:
103
+ logger.error(f"Error adding post: {str(e)}")
104
+ raise
105
+
106
+ def get_posts(self):
107
+ """Get all posts sorted by creation date (newest first)"""
108
+ try:
109
+ with self.lock:
110
+ posts = sorted(self.data['posts'], key=lambda x: x['created_at'], reverse=True)
111
+ logger.info(f"Retrieved {len(posts)} posts")
112
+ return posts
113
+ except Exception as e:
114
+ logger.error(f"Error getting posts: {str(e)}")
115
+ return []
116
+
117
+ def like_post(self, post_id):
118
+ """Like a post"""
119
+ try:
120
+ with self.lock:
121
+ for post in self.data['posts']:
122
+ if post['id'] == post_id:
123
+ post['likes'] += 1
124
+ self.save_data()
125
+ logger.info(f"Post {post_id} liked, total likes: {post['likes']}")
126
+ return post['likes']
127
+ logger.warning(f"Post {post_id} not found for liking")
128
+ return None
129
+ except Exception as e:
130
+ logger.error(f"Error liking post {post_id}: {str(e)}")
131
+ return None
132
+
133
+ def add_comment(self, post_id, name, comment):
134
+ """Add a comment to a post with UTC timestamp"""
135
+ try:
136
+ with self.lock:
137
+ # Check if post exists
138
+ post_exists = any(post['id'] == post_id for post in self.data['posts'])
139
+ if not post_exists:
140
+ logger.warning(f"Post {post_id} not found for commenting")
141
+ return None
142
+
143
+ comment_obj = {
144
+ 'id': self.data['next_comment_id'],
145
+ 'post_id': post_id,
146
+ 'name': str(name).strip(),
147
+ 'comment': str(comment).strip(),
148
+ 'created_at': self.get_utc_timestamp()
149
+ }
150
+ self.data['comments'].append(comment_obj)
151
+ self.data['next_comment_id'] += 1
152
+ self.save_data()
153
+ logger.info(f"Comment added to post {post_id}")
154
+ return comment_obj
155
+ except Exception as e:
156
+ logger.error(f"Error adding comment to post {post_id}: {str(e)}")
157
+ return None
158
+
159
+ def get_comments(self, post_id):
160
+ """Get comments for a specific post"""
161
+ try:
162
+ with self.lock:
163
+ comments = [c for c in self.data['comments'] if c['post_id'] == post_id]
164
+ logger.info(f"Retrieved {len(comments)} comments for post {post_id}")
165
+ return comments
166
+ except Exception as e:
167
+ logger.error(f"Error getting comments for post {post_id}: {str(e)}")
168
+ return []
169
+
170
+ def add_chat_message(self, name, message):
171
+ """Add a chat message with UTC timestamp"""
172
+ try:
173
+ with self.lock:
174
+ chat_msg = {
175
+ 'id': self.data['next_chat_id'],
176
+ 'name': str(name).strip(),
177
+ 'message': str(message).strip(),
178
+ 'created_at': self.get_utc_timestamp()
179
+ }
180
+ self.data['chat_messages'].append(chat_msg)
181
+ self.data['next_chat_id'] += 1
182
+
183
+ # Keep only last 100 chat messages to prevent memory issues
184
+ if len(self.data['chat_messages']) > 100:
185
+ self.data['chat_messages'] = self.data['chat_messages'][-100:]
186
+
187
+ self.save_data()
188
+ logger.info(f"Chat message added: ID {chat_msg['id']}, Name: {chat_msg['name']}")
189
+ return chat_msg
190
+ except Exception as e:
191
+ logger.error(f"Error adding chat message: {str(e)}")
192
+ return None
193
+
194
+ def get_chat_messages(self, limit=50):
195
+ """Get recent chat messages"""
196
+ try:
197
+ with self.lock:
198
+ messages = sorted(self.data['chat_messages'], key=lambda x: x['created_at'], reverse=True)[:limit]
199
+ messages.reverse() # Show oldest first
200
+ logger.info(f"Retrieved {len(messages)} chat messages")
201
+ return messages
202
+ except Exception as e:
203
+ logger.error(f"Error getting chat messages: {str(e)}")
204
+ return []
205
+
206
+ def get_stats(self):
207
+ """Get application statistics"""
208
+ try:
209
+ with self.lock:
210
+ return {
211
+ 'total_posts': len(self.data['posts']),
212
+ 'total_comments': len(self.data['comments']),
213
+ 'total_chat_messages': len(self.data['chat_messages']),
214
+ 'total_likes': sum(post['likes'] for post in self.data['posts']),
215
+ 'app_version': self.data.get('app_version', '2.0.0'),
216
+ 'last_backup': self.get_utc_timestamp(),
217
+ 'server_time': self.get_utc_timestamp()
218
+ }
219
+ except Exception as e:
220
+ logger.error(f"Error getting stats: {str(e)}")
221
+ return {
222
+ 'total_posts': 0,
223
+ 'total_comments': 0,
224
+ 'total_chat_messages': 0,
225
+ 'total_likes': 0,
226
+ 'app_version': '2.0.0',
227
+ 'last_backup': self.get_utc_timestamp(),
228
+ 'server_time': self.get_utc_timestamp()
229
+ }
230
+
231
+ # Initialize enhanced data store
232
+ data_store = EnhancedDataStore()
233
+
234
+ # Keep-alive mechanism
235
+ class KeepAlive:
236
+ def __init__(self):
237
+ self.last_activity = time.time()
238
+ self.keep_alive_thread = threading.Thread(target=self.keep_alive_loop, daemon=True)
239
+ self.keep_alive_thread.start()
240
+
241
+ def update_activity(self):
242
+ """Update last activity timestamp"""
243
+ self.last_activity = time.time()
244
+
245
+ def keep_alive_loop(self):
246
+ """Keep the application active"""
247
+ while True:
248
+ time.sleep(300) # Every 5 minutes
249
+ current_time = time.time()
250
+ if current_time - self.last_activity < 3600: # If activity within last hour
251
+ stats = data_store.get_stats()
252
+ logger.info(f"Keep-alive: App v{stats['app_version']} is active. Stats: {stats}")
253
+ time.sleep(300)
254
+
255
+ # Initialize keep-alive
256
+ keep_alive = KeepAlive()
257
+
258
+ # Enhanced HTML Template with Chat Tab and Time Sync
259
+ HTML_TEMPLATE = '''
260
+ <!DOCTYPE html>
261
+ <html lang="en">
262
+ <head>
263
+ <meta charset="UTF-8">
264
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
265
+ <title>Share YouTube v2.0 - Enhanced with Chat & Time Sync</title>
266
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
267
+ <style>
268
+ * {
269
+ margin: 0;
270
+ padding: 0;
271
+ box-sizing: border-box;
272
+ }
273
+
274
+ body {
275
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
276
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
277
+ min-height: 100vh;
278
+ color: #333;
279
+ }
280
+
281
+ .container {
282
+ max-width: 1000px;
283
+ margin: 0 auto;
284
+ padding: 20px;
285
+ }
286
+
287
+ .header {
288
+ text-align: center;
289
+ margin-bottom: 30px;
290
+ background: rgba(255, 255, 255, 0.95);
291
+ padding: 30px;
292
+ border-radius: 15px;
293
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
294
+ backdrop-filter: blur(10px);
295
+ }
296
+
297
+ .header h1 {
298
+ font-size: 2.5rem;
299
+ color: #FF0000;
300
+ margin-bottom: 10px;
301
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
302
+ }
303
+
304
+ .header p {
305
+ font-size: 1.1rem;
306
+ color: #666;
307
+ }
308
+
309
+ .version-badge {
310
+ display: inline-block;
311
+ background: #28a745;
312
+ color: white;
313
+ padding: 4px 12px;
314
+ border-radius: 20px;
315
+ font-size: 0.8rem;
316
+ margin-left: 10px;
317
+ }
318
+
319
+ .time-sync {
320
+ background: rgba(0, 123, 255, 0.1);
321
+ padding: 10px;
322
+ border-radius: 8px;
323
+ margin-top: 15px;
324
+ font-size: 0.9rem;
325
+ color: #007bff;
326
+ }
327
+
328
+ .stats-bar {
329
+ display: flex;
330
+ justify-content: space-around;
331
+ background: rgba(255, 255, 255, 0.9);
332
+ padding: 15px;
333
+ border-radius: 10px;
334
+ margin-top: 15px;
335
+ font-size: 0.9rem;
336
+ }
337
+
338
+ .stat-item {
339
+ text-align: center;
340
+ }
341
+
342
+ .stat-number {
343
+ font-weight: bold;
344
+ color: #FF0000;
345
+ font-size: 1.2rem;
346
+ }
347
+
348
+ .tabs {
349
+ display: flex;
350
+ background: rgba(255, 255, 255, 0.9);
351
+ border-radius: 15px 15px 0 0;
352
+ margin-bottom: 0;
353
+ overflow: hidden;
354
+ }
355
+
356
+ .tab {
357
+ flex: 1;
358
+ padding: 15px 20px;
359
+ background: rgba(255, 255, 255, 0.7);
360
+ border: none;
361
+ cursor: pointer;
362
+ font-size: 1rem;
363
+ font-weight: 600;
364
+ color: #666;
365
+ transition: all 0.3s ease;
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ gap: 8px;
370
+ }
371
+
372
+ .tab.active {
373
+ background: rgba(255, 255, 255, 1);
374
+ color: #FF0000;
375
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
376
+ }
377
+
378
+ .tab:hover {
379
+ background: rgba(255, 255, 255, 0.9);
380
+ }
381
+
382
+ .tab-content {
383
+ background: rgba(255, 255, 255, 0.95);
384
+ padding: 30px;
385
+ border-radius: 0 0 15px 15px;
386
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
387
+ backdrop-filter: blur(10px);
388
+ min-height: 500px;
389
+ }
390
+
391
+ .tab-pane {
392
+ display: none;
393
+ }
394
+
395
+ .tab-pane.active {
396
+ display: block;
397
+ }
398
+
399
+ .share-card h2,
400
+ .chat-card h2 {
401
+ color: #333;
402
+ margin-bottom: 25px;
403
+ font-size: 1.5rem;
404
+ text-align: center;
405
+ }
406
+
407
+ .form-group {
408
+ margin-bottom: 20px;
409
+ }
410
+
411
+ .form-group label {
412
+ display: block;
413
+ margin-bottom: 8px;
414
+ font-weight: 600;
415
+ color: #555;
416
+ }
417
+
418
+ .form-group input,
419
+ .form-group textarea {
420
+ width: 100%;
421
+ padding: 12px 15px;
422
+ border: 2px solid #e1e5e9;
423
+ border-radius: 10px;
424
+ font-size: 1rem;
425
+ transition: all 0.3s ease;
426
+ background: rgba(255, 255, 255, 0.9);
427
+ }
428
+
429
+ .form-group input:focus,
430
+ .form-group textarea:focus {
431
+ outline: none;
432
+ border-color: #FF0000;
433
+ box-shadow: 0 0 0 3px rgba(255, 0, 0, 0.1);
434
+ transform: translateY(-2px);
435
+ }
436
+
437
+ .form-group textarea {
438
+ resize: vertical;
439
+ min-height: 80px;
440
+ }
441
+
442
+ .btn-share,
443
+ .btn-send {
444
+ width: 100%;
445
+ padding: 15px;
446
+ background: linear-gradient(45deg, #FF0000, #CC0000);
447
+ color: white;
448
+ border: none;
449
+ border-radius: 10px;
450
+ font-size: 1.1rem;
451
+ font-weight: 600;
452
+ cursor: pointer;
453
+ transition: all 0.3s ease;
454
+ text-transform: uppercase;
455
+ letter-spacing: 1px;
456
+ }
457
+
458
+ .btn-send {
459
+ background: linear-gradient(45deg, #007bff, #0056b3);
460
+ }
461
+
462
+ .btn-share:hover,
463
+ .btn-send:hover {
464
+ transform: translateY(-2px);
465
+ box-shadow: 0 8px 25px rgba(255, 0, 0, 0.3);
466
+ }
467
+
468
+ .btn-send:hover {
469
+ box-shadow: 0 8px 25px rgba(0, 123, 255, 0.3);
470
+ }
471
+
472
+ .btn-share:disabled,
473
+ .btn-send:disabled {
474
+ background: #ccc;
475
+ cursor: not-allowed;
476
+ transform: none;
477
+ box-shadow: none;
478
+ }
479
+
480
+ .chat-container {
481
+ display: flex;
482
+ flex-direction: column;
483
+ height: 500px;
484
+ }
485
+
486
+ .chat-messages {
487
+ flex: 1;
488
+ background: #f8f9fa;
489
+ border: 1px solid #e1e5e9;
490
+ border-radius: 10px;
491
+ padding: 15px;
492
+ margin-bottom: 15px;
493
+ overflow-y: auto;
494
+ max-height: 350px;
495
+ }
496
+
497
+ .chat-message {
498
+ background: white;
499
+ padding: 10px 15px;
500
+ border-radius: 10px;
501
+ margin-bottom: 10px;
502
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
503
+ }
504
+
505
+ .chat-message-header {
506
+ display: flex;
507
+ justify-content: space-between;
508
+ align-items: center;
509
+ margin-bottom: 5px;
510
+ }
511
+
512
+ .chat-message-name {
513
+ font-weight: 600;
514
+ color: #007bff;
515
+ }
516
+
517
+ .chat-message-time {
518
+ font-size: 0.8rem;
519
+ color: #888;
520
+ }
521
+
522
+ .chat-message-text {
523
+ color: #555;
524
+ line-height: 1.4;
525
+ }
526
+
527
+ .chat-form {
528
+ display: flex;
529
+ gap: 10px;
530
+ }
531
+
532
+ .chat-input {
533
+ flex: 1;
534
+ padding: 12px 15px;
535
+ border: 2px solid #e1e5e9;
536
+ border-radius: 25px;
537
+ font-size: 1rem;
538
+ }
539
+
540
+ .chat-send-btn {
541
+ padding: 12px 20px;
542
+ background: #007bff;
543
+ color: white;
544
+ border: none;
545
+ border-radius: 25px;
546
+ cursor: pointer;
547
+ font-size: 1rem;
548
+ }
549
+
550
+ .link-card {
551
+ background: #fff;
552
+ border: 1px solid #e1e5e9;
553
+ border-radius: 12px;
554
+ padding: 20px;
555
+ margin-bottom: 20px;
556
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
557
+ transition: all 0.3s ease;
558
+ }
559
+
560
+ .link-card:hover {
561
+ transform: translateY(-3px);
562
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
563
+ }
564
+
565
+ .link-header {
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: space-between;
569
+ margin-bottom: 15px;
570
+ }
571
+
572
+ .link-header .user-name {
573
+ font-weight: 600;
574
+ color: #333;
575
+ }
576
+
577
+ .link-header .timestamp {
578
+ color: #888;
579
+ font-size: 0.9rem;
580
+ }
581
+
582
+ .link-note {
583
+ margin-bottom: 15px;
584
+ color: #555;
585
+ line-height: 1.5;
586
+ }
587
+
588
+ .youtube-embed {
589
+ margin-bottom: 15px;
590
+ border-radius: 8px;
591
+ overflow: hidden;
592
+ }
593
+
594
+ .youtube-embed iframe {
595
+ width: 100%;
596
+ height: 315px;
597
+ border: none;
598
+ }
599
+
600
+ .link-actions {
601
+ display: flex;
602
+ gap: 15px;
603
+ padding-top: 15px;
604
+ border-top: 1px solid #e1e5e9;
605
+ }
606
+
607
+ .action-btn {
608
+ background: none;
609
+ border: none;
610
+ padding: 8px 15px;
611
+ border-radius: 20px;
612
+ cursor: pointer;
613
+ transition: all 0.3s ease;
614
+ font-size: 0.9rem;
615
+ display: flex;
616
+ align-items: center;
617
+ gap: 5px;
618
+ }
619
+
620
+ .like-btn {
621
+ color: #666;
622
+ }
623
+
624
+ .like-btn:hover,
625
+ .like-btn.liked {
626
+ background: rgba(255, 0, 0, 0.1);
627
+ color: #FF0000;
628
+ }
629
+
630
+ .comment-btn {
631
+ color: #666;
632
+ }
633
+
634
+ .comment-btn:hover {
635
+ background: rgba(0, 123, 255, 0.1);
636
+ color: #007bff;
637
+ }
638
+
639
+ .comments-section {
640
+ margin-top: 15px;
641
+ padding-top: 15px;
642
+ border-top: 1px solid #e1e5e9;
643
+ }
644
+
645
+ .comment-form {
646
+ display: flex;
647
+ gap: 10px;
648
+ margin-bottom: 15px;
649
+ }
650
+
651
+ .comment-form input {
652
+ flex: 1;
653
+ padding: 8px 12px;
654
+ border: 1px solid #ddd;
655
+ border-radius: 20px;
656
+ font-size: 0.9rem;
657
+ }
658
+
659
+ .comment-form button {
660
+ padding: 8px 15px;
661
+ background: #007bff;
662
+ color: white;
663
+ border: none;
664
+ border-radius: 20px;
665
+ cursor: pointer;
666
+ font-size: 0.9rem;
667
+ }
668
+
669
+ .comment-form button:hover {
670
+ background: #0056b3;
671
+ }
672
+
673
+ .comment {
674
+ background: #f8f9fa;
675
+ padding: 10px 15px;
676
+ border-radius: 10px;
677
+ margin-bottom: 10px;
678
+ }
679
+
680
+ .comment-author {
681
+ font-weight: 600;
682
+ color: #333;
683
+ margin-bottom: 5px;
684
+ }
685
+
686
+ .comment-text {
687
+ color: #555;
688
+ font-size: 0.9rem;
689
+ }
690
+
691
+ .loading-spinner {
692
+ position: fixed;
693
+ top: 50%;
694
+ left: 50%;
695
+ transform: translate(-50%, -50%);
696
+ background: rgba(255, 255, 255, 0.95);
697
+ padding: 30px;
698
+ border-radius: 15px;
699
+ text-align: center;
700
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
701
+ display: none;
702
+ z-index: 1000;
703
+ }
704
+
705
+ .loading-spinner i {
706
+ font-size: 2rem;
707
+ color: #FF0000;
708
+ margin-bottom: 10px;
709
+ }
710
+
711
+ .success-message,
712
+ .error-message {
713
+ position: fixed;
714
+ top: 20px;
715
+ right: 20px;
716
+ padding: 15px 20px;
717
+ border-radius: 10px;
718
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
719
+ display: none;
720
+ align-items: center;
721
+ gap: 10px;
722
+ z-index: 1000;
723
+ }
724
+
725
+ .success-message {
726
+ background: #28a745;
727
+ color: white;
728
+ }
729
+
730
+ .error-message {
731
+ background: #dc3545;
732
+ color: white;
733
+ }
734
+
735
+ .success-message i,
736
+ .error-message i {
737
+ font-size: 1.2rem;
738
+ }
739
+
740
+ @media (max-width: 768px) {
741
+ .container {
742
+ padding: 15px;
743
+ }
744
+
745
+ .header h1 {
746
+ font-size: 2rem;
747
+ }
748
+
749
+ .tab-content {
750
+ padding: 20px;
751
+ }
752
+
753
+ .youtube-embed iframe {
754
+ height: 200px;
755
+ }
756
+
757
+ .link-actions {
758
+ flex-wrap: wrap;
759
+ }
760
+
761
+ .stats-bar {
762
+ flex-direction: column;
763
+ gap: 10px;
764
+ }
765
+
766
+ .chat-container {
767
+ height: 400px;
768
+ }
769
+
770
+ .chat-messages {
771
+ max-height: 250px;
772
+ }
773
+ }
774
+
775
+ @keyframes fadeInUp {
776
+ from {
777
+ opacity: 0;
778
+ transform: translateY(30px);
779
+ }
780
+ to {
781
+ opacity: 1;
782
+ transform: translateY(0);
783
+ }
784
+ }
785
+
786
+ .link-card,
787
+ .chat-message {
788
+ animation: fadeInUp 0.5s ease;
789
+ }
790
+ </style>
791
+ </head>
792
+ <body>
793
+ <div class="container">
794
+ <!-- Header -->
795
+ <header class="header">
796
+ <div class="header-content">
797
+ <h1>
798
+ <i class="fab fa-youtube"></i> Share YouTube
799
+ <span class="version-badge" id="versionBadge">v2.0</span>
800
+ </h1>
801
+ <p>Share videos, chat with friends - Enhanced with real-time sync!</p>
802
+ <div class="time-sync" id="timeSync">
803
+ <i class="fas fa-clock"></i>
804
+ <span>Syncing time with server...</span>
805
+ </div>
806
+ <div class="stats-bar" id="statsBar">
807
+ <div class="stat-item">
808
+ <div class="stat-number" id="totalPosts">0</div>
809
+ <div>Posts</div>
810
+ </div>
811
+ <div class="stat-item">
812
+ <div class="stat-number" id="totalComments">0</div>
813
+ <div>Comments</div>
814
+ </div>
815
+ <div class="stat-item">
816
+ <div class="stat-number" id="totalLikes">0</div>
817
+ <div>Likes</div>
818
+ </div>
819
+ <div class="stat-item">
820
+ <div class="stat-number" id="totalChatMessages">0</div>
821
+ <div>Chat Messages</div>
822
+ </div>
823
+ </div>
824
+ </div>
825
+ </header>
826
+
827
+ <!-- Tabs -->
828
+ <div class="tabs">
829
+ <button class="tab active" onclick="switchTab('share')">
830
+ <i class="fas fa-share"></i>
831
+ Share Videos
832
+ </button>
833
+ <button class="tab" onclick="switchTab('chat')">
834
+ <i class="fas fa-comments"></i>
835
+ Live Chat
836
+ </button>
837
+ </div>
838
+
839
+ <!-- Tab Content -->
840
+ <div class="tab-content">
841
+ <!-- Share Tab -->
842
+ <div id="shareTab" class="tab-pane active">
843
+ <div class="share-card">
844
+ <h2><i class="fas fa-share"></i> Share YouTube Link</h2>
845
+ <form id="shareForm">
846
+ <div class="form-group">
847
+ <label for="name"><i class="fas fa-user"></i> Your Name:</label>
848
+ <input type="text" id="name" name="name" required placeholder="Enter your name">
849
+ </div>
850
+
851
+ <div class="form-group">
852
+ <label for="youtube_link"><i class="fab fa-youtube"></i> YouTube Link:</label>
853
+ <input type="url" id="youtube_link" name="youtube_link" required placeholder="https://www.youtube.com/watch?v=...">
854
+ </div>
855
+
856
+ <div class="form-group">
857
+ <label for="note"><i class="fas fa-sticky-note"></i> Short Note:</label>
858
+ <textarea id="note" name="note" required placeholder="Write a short note about this video..."></textarea>
859
+ </div>
860
+
861
+ <button type="submit" class="btn-share" id="shareBtn">
862
+ <i class="fas fa-share"></i> Share
863
+ </button>
864
+ </form>
865
+ </div>
866
+
867
+ <!-- Shared Links Feed -->
868
+ <div style="margin-top: 30px;">
869
+ <h2 style="text-align: center; color: #333; margin-bottom: 25px;">
870
+ <i class="fas fa-list"></i> Shared Links
871
+ </h2>
872
+ <div id="linksContainer">
873
+ <!-- Shared links will be loaded here -->
874
+ </div>
875
+ </div>
876
+ </div>
877
+
878
+ <!-- Chat Tab -->
879
+ <div id="chatTab" class="tab-pane">
880
+ <div class="chat-card">
881
+ <h2><i class="fas fa-comments"></i> Live Chat</h2>
882
+ <div class="chat-container">
883
+ <div class="chat-messages" id="chatMessages">
884
+ <!-- Chat messages will be loaded here -->
885
+ </div>
886
+ <div class="chat-form">
887
+ <input type="text" id="chatInput" class="chat-input" placeholder="Type your message..." maxlength="500">
888
+ <button type="button" id="chatSendBtn" class="chat-send-btn">
889
+ <i class="fas fa-paper-plane"></i>
890
+ </button>
891
+ </div>
892
+ </div>
893
+ </div>
894
+ </div>
895
+ </div>
896
+ </div>
897
+
898
+ <!-- Loading Spinner -->
899
+ <div id="loadingSpinner" class="loading-spinner">
900
+ <i class="fas fa-spinner fa-spin"></i>
901
+ <p>Loading...</p>
902
+ </div>
903
+
904
+ <!-- Success Message -->
905
+ <div id="successMessage" class="success-message">
906
+ <i class="fas fa-check-circle"></i>
907
+ <span id="successText">Success!</span>
908
+ </div>
909
+
910
+ <!-- Error Message -->
911
+ <div id="errorMessage" class="error-message">
912
+ <i class="fas fa-exclamation-triangle"></i>
913
+ <span id="errorText">An error occurred!</span>
914
+ </div>
915
+
916
+ <script>
917
+ // API Base URL
918
+ const API_BASE_URL = '/api';
919
+
920
+ // Global variables
921
+ let serverTimeOffset = 0;
922
+ let currentTab = 'share';
923
+ let chatRefreshInterval;
924
+ let statsRefreshInterval;
925
+
926
+ // DOM Elements
927
+ const shareForm = document.getElementById('shareForm');
928
+ const shareBtn = document.getElementById('shareBtn');
929
+ const linksContainer = document.getElementById('linksContainer');
930
+ const chatMessages = document.getElementById('chatMessages');
931
+ const chatInput = document.getElementById('chatInput');
932
+ const chatSendBtn = document.getElementById('chatSendBtn');
933
+ const loadingSpinner = document.getElementById('loadingSpinner');
934
+ const successMessage = document.getElementById('successMessage');
935
+ const successText = document.getElementById('successText');
936
+ const errorMessage = document.getElementById('errorMessage');
937
+ const errorText = document.getElementById('errorText');
938
+ const timeSync = document.getElementById('timeSync');
939
+ const versionBadge = document.getElementById('versionBadge');
940
+
941
+ // Stats elements
942
+ const totalPosts = document.getElementById('totalPosts');
943
+ const totalComments = document.getElementById('totalComments');
944
+ const totalLikes = document.getElementById('totalLikes');
945
+ const totalChatMessages = document.getElementById('totalChatMessages');
946
+
947
+ // Initialize app
948
+ document.addEventListener('DOMContentLoaded', function() {
949
+ console.log('Enhanced Share YouTube App v2.0 initialized');
950
+
951
+ // Initialize time synchronization
952
+ syncTimeWithServer();
953
+
954
+ // Load initial data
955
+ loadStats();
956
+ loadLinks();
957
+ loadChatMessages();
958
+
959
+ // Set up event listeners
960
+ shareForm.addEventListener('submit', handleShareSubmit);
961
+ chatSendBtn.addEventListener('click', handleChatSend);
962
+ chatInput.addEventListener('keypress', function(e) {
963
+ if (e.key === 'Enter') {
964
+ handleChatSend();
965
+ }
966
+ });
967
+
968
+ // Set up periodic refreshes
969
+ statsRefreshInterval = setInterval(loadStats, 30000); // Every 30 seconds
970
+ chatRefreshInterval = setInterval(loadChatMessages, 5000); // Every 5 seconds for chat
971
+
972
+ // Sync time every 5 minutes
973
+ setInterval(syncTimeWithServer, 300000);
974
+ });
975
+
976
+ // Time synchronization
977
+ async function syncTimeWithServer() {
978
+ try {
979
+ const startTime = Date.now();
980
+ const response = await fetch('/api/time');
981
+ const endTime = Date.now();
982
+
983
+ if (response.ok) {
984
+ const data = await response.json();
985
+ const serverTime = new Date(data.server_time).getTime();
986
+ const networkDelay = (endTime - startTime) / 2;
987
+ const clientTime = endTime - networkDelay;
988
+
989
+ serverTimeOffset = serverTime - clientTime;
990
+
991
+ const localTime = new Date().toLocaleString();
992
+ const syncedTime = new Date(Date.now() + serverTimeOffset).toLocaleString();
993
+
994
+ timeSync.innerHTML = `
995
+ <i class="fas fa-clock"></i>
996
+ Time synced! Local: ${localTime} | Server: ${syncedTime}
997
+ `;
998
+
999
+ console.log('Time synchronized. Offset:', serverTimeOffset, 'ms');
1000
+ }
1001
+ } catch (error) {
1002
+ console.error('Time sync failed:', error);
1003
+ timeSync.innerHTML = `
1004
+ <i class="fas fa-exclamation-triangle"></i>
1005
+ Time sync failed - using local time
1006
+ `;
1007
+ }
1008
+ }
1009
+
1010
+ // Get synchronized time
1011
+ function getSyncedTime() {
1012
+ return new Date(Date.now() + serverTimeOffset);
1013
+ }
1014
+
1015
+ // Tab switching
1016
+ function switchTab(tabName) {
1017
+ // Update tab buttons
1018
+ document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
1019
+ event.target.classList.add('active');
1020
+
1021
+ // Update tab content
1022
+ document.querySelectorAll('.tab-pane').forEach(pane => pane.classList.remove('active'));
1023
+ document.getElementById(tabName + 'Tab').classList.add('active');
1024
+
1025
+ currentTab = tabName;
1026
+
1027
+ // Load data for the active tab
1028
+ if (tabName === 'chat') {
1029
+ loadChatMessages();
1030
+ // Refresh chat more frequently when viewing
1031
+ clearInterval(chatRefreshInterval);
1032
+ chatRefreshInterval = setInterval(loadChatMessages, 3000);
1033
+ } else {
1034
+ // Slower refresh when not viewing chat
1035
+ clearInterval(chatRefreshInterval);
1036
+ chatRefreshInterval = setInterval(loadChatMessages, 10000);
1037
+ }
1038
+ }
1039
+
1040
+ // Load statistics
1041
+ async function loadStats() {
1042
+ try {
1043
+ const response = await fetch('/api/stats');
1044
+ if (response.ok) {
1045
+ const stats = await response.json();
1046
+ totalPosts.textContent = stats.total_posts;
1047
+ totalComments.textContent = stats.total_comments;
1048
+ totalLikes.textContent = stats.total_likes;
1049
+ totalChatMessages.textContent = stats.total_chat_messages;
1050
+ versionBadge.textContent = 'v' + stats.app_version;
1051
+ console.log('Stats loaded:', stats);
1052
+ }
1053
+ } catch (error) {
1054
+ console.error('Error loading stats:', error);
1055
+ }
1056
+ }
1057
+
1058
+ // Handle share form submission
1059
+ async function handleShareSubmit(e) {
1060
+ e.preventDefault();
1061
+ console.log('Form submitted');
1062
+
1063
+ const formData = new FormData(shareForm);
1064
+ const data = {
1065
+ name: formData.get('name').trim(),
1066
+ note: formData.get('note').trim(),
1067
+ youtube_link: formData.get('youtube_link').trim()
1068
+ };
1069
+
1070
+ console.log('Form data:', data);
1071
+
1072
+ // Validate inputs
1073
+ if (!data.name || !data.note || !data.youtube_link) {
1074
+ showErrorMessage('Please fill in all fields');
1075
+ return;
1076
+ }
1077
+
1078
+ // Validate YouTube URL
1079
+ if (!isValidYouTubeURL(data.youtube_link)) {
1080
+ showErrorMessage('Please enter a valid YouTube URL');
1081
+ return;
1082
+ }
1083
+
1084
+ try {
1085
+ showLoading(true);
1086
+ shareBtn.disabled = true;
1087
+ shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sharing...';
1088
+
1089
+ const response = await fetch(`${API_BASE_URL}/posts`, {
1090
+ method: 'POST',
1091
+ headers: {
1092
+ 'Content-Type': 'application/json',
1093
+ },
1094
+ body: JSON.stringify(data)
1095
+ });
1096
+
1097
+ if (!response.ok) {
1098
+ const errorData = await response.json().catch(() => ({}));
1099
+ throw new Error(errorData.error || `HTTP ${response.status}`);
1100
+ }
1101
+
1102
+ const result = await response.json();
1103
+ console.log('Post created:', result);
1104
+
1105
+ showSuccessMessage('Shared successfully!');
1106
+ shareForm.reset();
1107
+
1108
+ await Promise.all([loadLinks(), loadStats()]);
1109
+
1110
+ } catch (error) {
1111
+ console.error('Error sharing link:', error);
1112
+ showErrorMessage('Error sharing link: ' + error.message);
1113
+ } finally {
1114
+ showLoading(false);
1115
+ shareBtn.disabled = false;
1116
+ shareBtn.innerHTML = '<i class="fas fa-share"></i> Share';
1117
+ }
1118
+ }
1119
+
1120
+ // Handle chat message send
1121
+ async function handleChatSend() {
1122
+ const message = chatInput.value.trim();
1123
+ if (!message) {
1124
+ showErrorMessage('Please enter a message');
1125
+ return;
1126
+ }
1127
+
1128
+ const name = prompt('Please enter your name:');
1129
+ if (!name || !name.trim()) return;
1130
+
1131
+ try {
1132
+ chatSendBtn.disabled = true;
1133
+ chatSendBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
1134
+
1135
+ const response = await fetch(`${API_BASE_URL}/chat`, {
1136
+ method: 'POST',
1137
+ headers: {
1138
+ 'Content-Type': 'application/json',
1139
+ },
1140
+ body: JSON.stringify({
1141
+ name: name.trim(),
1142
+ message: message
1143
+ })
1144
+ });
1145
+
1146
+ if (!response.ok) {
1147
+ const errorData = await response.json().catch(() => ({}));
1148
+ throw new Error(errorData.error || `HTTP ${response.status}`);
1149
+ }
1150
+
1151
+ chatInput.value = '';
1152
+ await Promise.all([loadChatMessages(), loadStats()]);
1153
+
1154
+ // Scroll to bottom of chat
1155
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1156
+
1157
+ } catch (error) {
1158
+ console.error('Error sending chat message:', error);
1159
+ showErrorMessage('Error sending message: ' + error.message);
1160
+ } finally {
1161
+ chatSendBtn.disabled = false;
1162
+ chatSendBtn.innerHTML = '<i class="fas fa-paper-plane"></i>';
1163
+ }
1164
+ }
1165
+
1166
+ // Load chat messages
1167
+ async function loadChatMessages() {
1168
+ try {
1169
+ const response = await fetch(`${API_BASE_URL}/chat`);
1170
+ if (!response.ok) {
1171
+ throw new Error(`HTTP ${response.status}`);
1172
+ }
1173
+
1174
+ const messages = await response.json();
1175
+ displayChatMessages(messages);
1176
+
1177
+ } catch (error) {
1178
+ console.error('Error loading chat messages:', error);
1179
+ if (currentTab === 'chat') {
1180
+ chatMessages.innerHTML = `
1181
+ <div style="text-align: center; color: #666; padding: 20px;">
1182
+ <i class="fas fa-exclamation-triangle"></i>
1183
+ <p>Failed to load chat messages</p>
1184
+ <button onclick="loadChatMessages()" style="margin-top: 10px; padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
1185
+ Retry
1186
+ </button>
1187
+ </div>
1188
+ `;
1189
+ }
1190
+ }
1191
+ }
1192
+
1193
+ // Display chat messages
1194
+ function displayChatMessages(messages) {
1195
+ if (messages.length === 0) {
1196
+ chatMessages.innerHTML = `
1197
+ <div style="text-align: center; color: #666; padding: 40px;">
1198
+ <i class="fas fa-comments" style="font-size: 3rem; margin-bottom: 15px;"></i>
1199
+ <p>No messages yet</p>
1200
+ <p>Be the first to start the conversation!</p>
1201
+ </div>
1202
+ `;
1203
+ return;
1204
+ }
1205
+
1206
+ const shouldScrollToBottom = chatMessages.scrollTop + chatMessages.clientHeight >= chatMessages.scrollHeight - 10;
1207
+
1208
+ chatMessages.innerHTML = messages.map(msg => `
1209
+ <div class="chat-message">
1210
+ <div class="chat-message-header">
1211
+ <span class="chat-message-name">
1212
+ <i class="fas fa-user"></i> ${escapeHtml(msg.name)}
1213
+ </span>
1214
+ <span class="chat-message-time">${formatTime(msg.created_at)}</span>
1215
+ </div>
1216
+ <div class="chat-message-text">${escapeHtml(msg.message)}</div>
1217
+ </div>
1218
+ `).join('');
1219
+
1220
+ if (shouldScrollToBottom) {
1221
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1222
+ }
1223
+ }
1224
+
1225
+ // Load all shared links
1226
+ async function loadLinks() {
1227
+ try {
1228
+ const response = await fetch(`${API_BASE_URL}/posts`);
1229
+ if (!response.ok) {
1230
+ throw new Error(`HTTP ${response.status}`);
1231
+ }
1232
+
1233
+ const links = await response.json();
1234
+ displayLinks(links);
1235
+
1236
+ } catch (error) {
1237
+ console.error('Error loading links:', error);
1238
+ linksContainer.innerHTML = `
1239
+ <div style="text-align: center; color: #666; padding: 20px;">
1240
+ <i class="fas fa-exclamation-triangle"></i>
1241
+ <p>Failed to load links: ${error.message}</p>
1242
+ <button onclick="loadLinks()" style="margin-top: 10px; padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
1243
+ Retry
1244
+ </button>
1245
+ </div>
1246
+ `;
1247
+ }
1248
+ }
1249
+
1250
+ // Display links in the container
1251
+ function displayLinks(links) {
1252
+ if (links.length === 0) {
1253
+ linksContainer.innerHTML = `
1254
+ <div style="text-align: center; color: #666; padding: 40px;">
1255
+ <i class="fab fa-youtube" style="font-size: 3rem; margin-bottom: 15px;"></i>
1256
+ <p>No shared links yet</p>
1257
+ <p>Be the first to share a YouTube link!</p>
1258
+ </div>
1259
+ `;
1260
+ return;
1261
+ }
1262
+
1263
+ linksContainer.innerHTML = links.map(link => createLinkCard(link)).join('');
1264
+ addLinkEventListeners();
1265
+ }
1266
+
1267
+ // Create HTML for a single link card
1268
+ function createLinkCard(link) {
1269
+ const videoId = extractYouTubeVideoId(link.youtube_link);
1270
+ const embedUrl = videoId ? `https://www.youtube.com/embed/${videoId}` : '';
1271
+ const timeAgo = formatTime(link.created_at);
1272
+
1273
+ return `
1274
+ <div class="link-card" data-link-id="${link.id}">
1275
+ <div class="link-header">
1276
+ <span class="user-name">
1277
+ <i class="fas fa-user"></i> ${escapeHtml(link.name)}
1278
+ </span>
1279
+ <span class="timestamp">${timeAgo}</span>
1280
+ </div>
1281
+
1282
+ <div class="link-note">
1283
+ ${escapeHtml(link.note)}
1284
+ </div>
1285
+
1286
+ ${embedUrl ? `
1287
+ <div class="youtube-embed">
1288
+ <iframe src="${embedUrl}"
1289
+ title="YouTube video player"
1290
+ frameborder="0"
1291
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
1292
+ allowfullscreen>
1293
+ </iframe>
1294
+ </div>
1295
+ ` : `
1296
+ <div class="youtube-link">
1297
+ <a href="${link.youtube_link}" target="_blank" rel="noopener noreferrer">
1298
+ <i class="fab fa-youtube"></i> Watch on YouTube
1299
+ </a>
1300
+ </div>
1301
+ `}
1302
+
1303
+ <div class="link-actions">
1304
+ <button class="action-btn like-btn" data-link-id="${link.id}">
1305
+ <i class="fas fa-heart"></i>
1306
+ <span class="like-count">${link.likes}</span>
1307
+ </button>
1308
+ <button class="action-btn comment-btn" data-link-id="${link.id}">
1309
+ <i class="fas fa-comment"></i>
1310
+ Comment
1311
+ </button>
1312
+ </div>
1313
+
1314
+ <div class="comments-section" id="comments-${link.id}" style="display: none;">
1315
+ <div class="comment-form">
1316
+ <input type="text" placeholder="Write a comment..." class="comment-input">
1317
+ <button type="button" class="add-comment-btn" data-link-id="${link.id}">
1318
+ <i class="fas fa-paper-plane"></i>
1319
+ </button>
1320
+ </div>
1321
+ <div class="comments-list" id="comments-list-${link.id}">
1322
+ <!-- Comments will be loaded here -->
1323
+ </div>
1324
+ </div>
1325
+ </div>
1326
+ `;
1327
+ }
1328
+
1329
+ // Add event listeners for link interactions
1330
+ function addLinkEventListeners() {
1331
+ document.querySelectorAll('.like-btn').forEach(btn => {
1332
+ btn.addEventListener('click', handleLike);
1333
+ });
1334
+
1335
+ document.querySelectorAll('.comment-btn').forEach(btn => {
1336
+ btn.addEventListener('click', toggleComments);
1337
+ });
1338
+
1339
+ document.querySelectorAll('.add-comment-btn').forEach(btn => {
1340
+ btn.addEventListener('click', handleAddComment);
1341
+ });
1342
+
1343
+ document.querySelectorAll('.comment-input').forEach(input => {
1344
+ input.addEventListener('keypress', function(e) {
1345
+ if (e.key === 'Enter') {
1346
+ const linkId = this.closest('.comments-section').id.split('-')[1];
1347
+ const btn = document.querySelector(`.add-comment-btn[data-link-id="${linkId}"]`);
1348
+ btn.click();
1349
+ }
1350
+ });
1351
+ });
1352
+ }
1353
+
1354
+ // Handle like button click
1355
+ async function handleLike(e) {
1356
+ const linkId = e.currentTarget.dataset.linkId;
1357
+ const likeBtn = e.currentTarget;
1358
+ const likeCount = likeBtn.querySelector('.like-count');
1359
+
1360
+ try {
1361
+ const response = await fetch(`${API_BASE_URL}/posts/${linkId}/like`, {
1362
+ method: 'POST'
1363
+ });
1364
+
1365
+ if (!response.ok) {
1366
+ throw new Error(`HTTP ${response.status}`);
1367
+ }
1368
+
1369
+ const result = await response.json();
1370
+ likeCount.textContent = result.likes;
1371
+
1372
+ likeBtn.classList.add('liked');
1373
+ setTimeout(() => likeBtn.classList.remove('liked'), 1000);
1374
+
1375
+ loadStats();
1376
+
1377
+ } catch (error) {
1378
+ console.error('Error liking post:', error);
1379
+ showErrorMessage('Error liking post');
1380
+ }
1381
+ }
1382
+
1383
+ // Toggle comments section
1384
+ async function toggleComments(e) {
1385
+ const linkId = e.currentTarget.dataset.linkId;
1386
+ const commentsSection = document.getElementById(`comments-${linkId}`);
1387
+
1388
+ if (commentsSection.style.display === 'none') {
1389
+ commentsSection.style.display = 'block';
1390
+ await loadComments(linkId);
1391
+ } else {
1392
+ commentsSection.style.display = 'none';
1393
+ }
1394
+ }
1395
+
1396
+ // Load comments for a specific link
1397
+ async function loadComments(linkId) {
1398
+ try {
1399
+ const response = await fetch(`${API_BASE_URL}/posts/${linkId}/comments`);
1400
+ if (!response.ok) {
1401
+ throw new Error(`HTTP ${response.status}`);
1402
+ }
1403
+
1404
+ const comments = await response.json();
1405
+ const commentsList = document.getElementById(`comments-list-${linkId}`);
1406
+
1407
+ if (comments.length === 0) {
1408
+ commentsList.innerHTML = '<p style="text-align: center; color: #666; padding: 10px;">No comments yet</p>';
1409
+ } else {
1410
+ commentsList.innerHTML = comments.map(comment => `
1411
+ <div class="comment">
1412
+ <div class="comment-author">
1413
+ <i class="fas fa-user"></i> ${escapeHtml(comment.name)}
1414
+ <span style="color: #888; font-size: 0.8rem; margin-left: 10px;">${formatTime(comment.created_at)}</span>
1415
+ </div>
1416
+ <div class="comment-text">${escapeHtml(comment.comment)}</div>
1417
+ </div>
1418
+ `).join('');
1419
+ }
1420
+
1421
+ } catch (error) {
1422
+ console.error('Error loading comments:', error);
1423
+ }
1424
+ }
1425
+
1426
+ // Handle add comment
1427
+ async function handleAddComment(e) {
1428
+ const linkId = e.currentTarget.dataset.linkId;
1429
+ const commentsSection = document.getElementById(`comments-${linkId}`);
1430
+ const commentInput = commentsSection.querySelector('.comment-input');
1431
+ const commentText = commentInput.value.trim();
1432
+
1433
+ if (!commentText) {
1434
+ showErrorMessage('Please write a comment');
1435
+ return;
1436
+ }
1437
+
1438
+ const name = prompt('Please enter your name:');
1439
+ if (!name || !name.trim()) return;
1440
+
1441
+ try {
1442
+ const response = await fetch(`${API_BASE_URL}/posts/${linkId}/comments`, {
1443
+ method: 'POST',
1444
+ headers: {
1445
+ 'Content-Type': 'application/json',
1446
+ },
1447
+ body: JSON.stringify({
1448
+ name: name.trim(),
1449
+ comment: commentText
1450
+ })
1451
+ });
1452
+
1453
+ if (!response.ok) {
1454
+ throw new Error(`HTTP ${response.status}`);
1455
+ }
1456
+
1457
+ commentInput.value = '';
1458
+ await Promise.all([loadComments(linkId), loadStats()]);
1459
+ showSuccessMessage('Comment added successfully!');
1460
+
1461
+ } catch (error) {
1462
+ console.error('Error adding comment:', error);
1463
+ showErrorMessage('Error adding comment');
1464
+ }
1465
+ }
1466
+
1467
+ // Utility functions
1468
+ function isValidYouTubeURL(url) {
1469
+ const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)[a-zA-Z0-9_-]{11}/;
1470
+ return youtubeRegex.test(url);
1471
+ }
1472
+
1473
+ function extractYouTubeVideoId(url) {
1474
+ const regex = /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/;
1475
+ const match = url.match(regex);
1476
+ return match ? match[1] : null;
1477
+ }
1478
+
1479
+ function escapeHtml(text) {
1480
+ const div = document.createElement('div');
1481
+ div.textContent = text;
1482
+ return div.innerHTML;
1483
+ }
1484
+
1485
+ function formatTime(utcTimeString) {
1486
+ try {
1487
+ const utcTime = new Date(utcTimeString);
1488
+ const now = getSyncedTime();
1489
+ const diffInSeconds = Math.floor((now - utcTime) / 1000);
1490
+
1491
+ if (diffInSeconds < 60) return 'just now';
1492
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
1493
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
1494
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`;
1495
+
1496
+ // For older posts, show the actual date
1497
+ return utcTime.toLocaleDateString() + ' ' + utcTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
1498
+ } catch (error) {
1499
+ console.error('Error formatting time:', error);
1500
+ return 'unknown time';
1501
+ }
1502
+ }
1503
+
1504
+ function showLoading(show) {
1505
+ loadingSpinner.style.display = show ? 'block' : 'none';
1506
+ }
1507
+
1508
+ function showSuccessMessage(message) {
1509
+ successText.textContent = message;
1510
+ successMessage.style.display = 'flex';
1511
+ setTimeout(() => {
1512
+ successMessage.style.display = 'none';
1513
+ }, 3000);
1514
+ }
1515
+
1516
+ function showErrorMessage(message) {
1517
+ errorText.textContent = message;
1518
+ errorMessage.style.display = 'flex';
1519
+ setTimeout(() => {
1520
+ errorMessage.style.display = 'none';
1521
+ }, 5000);
1522
+ }
1523
+
1524
+ // Cleanup intervals when page unloads
1525
+ window.addEventListener('beforeunload', function() {
1526
+ clearInterval(statsRefreshInterval);
1527
+ clearInterval(chatRefreshInterval);
1528
+ });
1529
+ </script>
1530
+ </body>
1531
+ </html>
1532
+ '''
1533
+
1534
+ # Enhanced API Routes
1535
+ @app.route('/api/time', methods=['GET'])
1536
+ def get_server_time():
1537
+ """Get server time for synchronization"""
1538
+ try:
1539
+ keep_alive.update_activity()
1540
+ return jsonify({
1541
+ 'server_time': data_store.get_utc_timestamp(),
1542
+ 'timezone': 'UTC'
1543
+ })
1544
+ except Exception as e:
1545
+ logger.error(f"Error getting server time: {str(e)}")
1546
+ return jsonify({'error': 'Failed to get server time'}), 500
1547
+
1548
+ @app.route('/api/posts', methods=['GET'])
1549
+ def get_posts():
1550
+ """Get all posts"""
1551
+ try:
1552
+ keep_alive.update_activity()
1553
+ posts = data_store.get_posts()
1554
+ logger.info(f"API: Returning {len(posts)} posts")
1555
+ return jsonify(posts)
1556
+ except Exception as e:
1557
+ logger.error(f"API Error getting posts: {str(e)}")
1558
+ return jsonify({'error': 'Failed to get posts'}), 500
1559
+
1560
+ @app.route('/api/posts', methods=['POST'])
1561
+ def create_post():
1562
+ """Create a new post"""
1563
+ try:
1564
+ keep_alive.update_activity()
1565
+ data = request.get_json()
1566
+ logger.info(f"API: Received post data: {data}")
1567
+
1568
+ if not data:
1569
+ logger.warning("API: No JSON data received")
1570
+ return jsonify({'error': 'No data provided'}), 400
1571
+
1572
+ name = data.get('name', '').strip()
1573
+ note = data.get('note', '').strip()
1574
+ youtube_link = data.get('youtube_link', '').strip()
1575
+
1576
+ if not name or not note:
1577
+ logger.warning(f"API: Missing required fields - name: {bool(name)}, note: {bool(note)}")
1578
+ return jsonify({'error': 'Name and note are required'}), 400
1579
+
1580
+ post = data_store.add_post(name=name, note=note, youtube_link=youtube_link)
1581
+ logger.info(f"API: Post created successfully: {post['id']}")
1582
+ return jsonify(post), 201
1583
+
1584
+ except Exception as e:
1585
+ logger.error(f"API Error creating post: {str(e)}")
1586
+ return jsonify({'error': f'Failed to create post: {str(e)}'}), 500
1587
+
1588
+ @app.route('/api/posts/<int:post_id>/like', methods=['POST'])
1589
+ def like_post(post_id):
1590
+ """Like a post"""
1591
+ try:
1592
+ keep_alive.update_activity()
1593
+ likes = data_store.like_post(post_id)
1594
+
1595
+ if likes is None:
1596
+ logger.warning(f"API: Post {post_id} not found for liking")
1597
+ return jsonify({'error': 'Post not found'}), 404
1598
+
1599
+ logger.info(f"API: Post {post_id} liked, total likes: {likes}")
1600
+ return jsonify({'likes': likes})
1601
+
1602
+ except Exception as e:
1603
+ logger.error(f"API Error liking post {post_id}: {str(e)}")
1604
+ return jsonify({'error': 'Failed to like post'}), 500
1605
+
1606
+ @app.route('/api/posts/<int:post_id>/comments', methods=['GET'])
1607
+ def get_comments(post_id):
1608
+ """Get comments for a post"""
1609
+ try:
1610
+ keep_alive.update_activity()
1611
+ comments = data_store.get_comments(post_id)
1612
+ logger.info(f"API: Returning {len(comments)} comments for post {post_id}")
1613
+ return jsonify(comments)
1614
+
1615
+ except Exception as e:
1616
+ logger.error(f"API Error getting comments for post {post_id}: {str(e)}")
1617
+ return jsonify({'error': 'Failed to get comments'}), 500
1618
+
1619
+ @app.route('/api/posts/<int:post_id>/comments', methods=['POST'])
1620
+ def add_comment(post_id):
1621
+ """Add a comment to a post"""
1622
+ try:
1623
+ keep_alive.update_activity()
1624
+ data = request.get_json()
1625
+ logger.info(f"API: Received comment data for post {post_id}: {data}")
1626
+
1627
+ if not data:
1628
+ return jsonify({'error': 'No data provided'}), 400
1629
+
1630
+ name = data.get('name', '').strip()
1631
+ comment_text = data.get('comment', '').strip()
1632
+
1633
+ if not name or not comment_text:
1634
+ logger.warning(f"API: Missing required fields - name: {bool(name)}, comment: {bool(comment_text)}")
1635
+ return jsonify({'error': 'Name and comment are required'}), 400
1636
+
1637
+ comment = data_store.add_comment(post_id=post_id, name=name, comment=comment_text)
1638
+
1639
+ if comment is None:
1640
+ logger.warning(f"API: Post {post_id} not found for commenting")
1641
+ return jsonify({'error': 'Post not found'}), 404
1642
+
1643
+ logger.info(f"API: Comment added to post {post_id}")
1644
+ return jsonify(comment), 201
1645
+
1646
+ except Exception as e:
1647
+ logger.error(f"API Error adding comment to post {post_id}: {str(e)}")
1648
+ return jsonify({'error': 'Failed to add comment'}), 500
1649
+
1650
+ @app.route('/api/chat', methods=['GET'])
1651
+ def get_chat_messages():
1652
+ """Get chat messages"""
1653
+ try:
1654
+ keep_alive.update_activity()
1655
+ messages = data_store.get_chat_messages()
1656
+ logger.info(f"API: Returning {len(messages)} chat messages")
1657
+ return jsonify(messages)
1658
+ except Exception as e:
1659
+ logger.error(f"API Error getting chat messages: {str(e)}")
1660
+ return jsonify({'error': 'Failed to get chat messages'}), 500
1661
+
1662
+ @app.route('/api/chat', methods=['POST'])
1663
+ def add_chat_message():
1664
+ """Add a chat message"""
1665
+ try:
1666
+ keep_alive.update_activity()
1667
+ data = request.get_json()
1668
+ logger.info(f"API: Received chat message data: {data}")
1669
+
1670
+ if not data:
1671
+ return jsonify({'error': 'No data provided'}), 400
1672
+
1673
+ name = data.get('name', '').strip()
1674
+ message = data.get('message', '').strip()
1675
+
1676
+ if not name or not message:
1677
+ logger.warning(f"API: Missing required fields - name: {bool(name)}, message: {bool(message)}")
1678
+ return jsonify({'error': 'Name and message are required'}), 400
1679
+
1680
+ chat_msg = data_store.add_chat_message(name=name, message=message)
1681
+
1682
+ if chat_msg is None:
1683
+ return jsonify({'error': 'Failed to add chat message'}), 500
1684
+
1685
+ logger.info(f"API: Chat message added: {chat_msg['id']}")
1686
+ return jsonify(chat_msg), 201
1687
+
1688
+ except Exception as e:
1689
+ logger.error(f"API Error adding chat message: {str(e)}")
1690
+ return jsonify({'error': 'Failed to add chat message'}), 500
1691
+
1692
+ @app.route('/api/stats', methods=['GET'])
1693
+ def get_stats():
1694
+ """Get application statistics"""
1695
+ try:
1696
+ keep_alive.update_activity()
1697
+ stats = data_store.get_stats()
1698
+ logger.info(f"API: Returning stats: {stats}")
1699
+ return jsonify(stats)
1700
+ except Exception as e:
1701
+ logger.error(f"API Error getting stats: {str(e)}")
1702
+ return jsonify({'error': 'Failed to get stats'}), 500
1703
+
1704
+ # Health check endpoint
1705
+ @app.route('/health')
1706
+ def health_check():
1707
+ """Health check endpoint"""
1708
+ try:
1709
+ keep_alive.update_activity()
1710
+ stats = data_store.get_stats()
1711
+ return jsonify({
1712
+ 'status': 'healthy',
1713
+ 'timestamp': data_store.get_utc_timestamp(),
1714
+ 'stats': stats
1715
+ })
1716
+ except Exception as e:
1717
+ logger.error(f"Health check error: {str(e)}")
1718
+ return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
1719
+
1720
+ # Main route
1721
+ @app.route('/')
1722
+ def index():
1723
+ keep_alive.update_activity()
1724
+ return render_template_string(HTML_TEMPLATE)
1725
+
1726
+ # Error handlers
1727
+ @app.errorhandler(404)
1728
+ def not_found(error):
1729
+ logger.warning(f"404 error: {request.url}")
1730
+ return jsonify({'error': 'Not found'}), 404
1731
+
1732
+ @app.errorhandler(500)
1733
+ def internal_error(error):
1734
+ logger.error(f"500 error: {str(error)}")
1735
+ return jsonify({'error': 'Internal server error'}), 500
1736
+
1737
+ if __name__ == '__main__':
1738
+ port = int(os.environ.get('PORT', 7860))
1739
+
1740
+ logger.info(f"===== Enhanced Share YouTube App v2.0 Startup at {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} =====")
1741
+ logger.info(f"Starting enhanced server with chat and time sync on port {port}")
1742
+ logger.info(f"Data will be saved to: {data_store.backup_file}")
1743
+
1744
+ # Load initial data and show stats
1745
+ initial_stats = data_store.get_stats()
1746
+ logger.info(f"Initial stats: {initial_stats}")
1747
+
1748
+ # Run with threading enabled and proper error handling
1749
+ run_simple(
1750
+ hostname='0.0.0.0',
1751
+ port=port,
1752
+ application=app,
1753
+ use_reloader=False,
1754
+ use_debugger=False,
1755
+ threaded=True,
1756
+ processes=1
1757
+ )
1758
+