Eluza133 commited on
Commit
ae01937
·
verified ·
1 Parent(s): 0406ca3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +888 -193
app.py CHANGED
@@ -977,7 +977,7 @@ def feed():
977
  </video>
978
  <button class="play-btn" onclick="playVideo('{{ post['id'] }}')">▶</button>
979
  {% else %}
980
- <img class="post-media" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
981
  {% endif %}
982
  <div class="post-overlay">
983
  <h2>{{ post['title'] }}</h2>
@@ -1120,8 +1120,7 @@ def feed():
1120
  user_count=user_count,
1121
  private_unread_count=private_unread_count,
1122
  is_online=is_online,
1123
- is_user_online=lambda u: is_user_online(data, u),
1124
- data=data)
1125
 
1126
  @app.route('/post/<post_id>', methods=['GET', 'POST'])
1127
  def post_page(post_id):
@@ -1255,6 +1254,7 @@ def post_page(post_id):
1255
  <button class="btn like-btn {% if username in post['likes'] %}liked{% endif %}" onclick="toggleLike('{{ post['id'] }}')">
1256
  {% if username in post['likes'] %}Unlike{% else %}Like{% endif %}
1257
  </button>
 
1258
  <form method="POST" class="comment-section">
1259
  <textarea name="comment" placeholder="Leave a comment" rows="3"></textarea>
1260
  <button type="submit" class="btn">Submit</button>
@@ -1307,6 +1307,11 @@ def post_page(post_id):
1307
  })
1308
  .catch(error => console.error('Error:', error));
1309
  }
 
 
 
 
 
1310
  window.onload = () => {
1311
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
1312
  };
@@ -1467,6 +1472,13 @@ def profile():
1467
  .post-item a {
1468
  text-decoration: none;
1469
  }
 
 
 
 
 
 
 
1470
  @media (max-width: 900px) {
1471
  .profile-header {
1472
  flex-direction: column;
@@ -1513,6 +1525,13 @@ def profile():
1513
  {% endif %}
1514
  <p>Total Views: {{ total_views }} | Total Likes: {{ total_likes }}</p>
1515
  <p>Last Seen: {{ last_seen }}</p>
 
 
 
 
 
 
 
1516
  </div>
1517
  </div>
1518
  <h2 style="font-size: 1.9em; margin-bottom: 25px;">Posts</h2>
@@ -1531,6 +1550,11 @@ def profile():
1531
  </a>
1532
  <p>{{ post['description']|truncate(100) }}</p>
1533
  <p>{{ post['upload_date'] }} | Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
 
 
 
 
 
1534
  </div>
1535
  {% endfor %}
1536
  {% if not user_posts %}
@@ -1591,70 +1615,31 @@ def profile():
1591
  private_unread_count=private_unread_count,
1592
  is_online=is_online)
1593
 
1594
- @app.route('/upload', methods=['GET', 'POST'])
1595
- def upload():
1596
- if 'username' not in session:
1597
- flash('Войдите, чтобы загрузить контент!')
1598
- return redirect(url_for('login'))
1599
-
1600
  data = load_data()
1601
- username = session['username']
1602
- update_last_seen(data, username)
 
 
 
 
 
1603
  is_authenticated = 'username' in session
1604
- unread_count = get_unread_count(data, username)
1605
- private_unread_count = get_private_unread_count(data, username)
1606
  user_count = len(data['users'])
1607
- is_online = is_user_online(data, username)
1608
-
1609
- if request.method == 'POST':
1610
- title = request.form.get('title')
1611
- description = request.form.get('description')
1612
- file = request.files.get('file')
1613
-
1614
- if not file or not title:
1615
- flash('Название и файл обязательны!')
1616
- return redirect(url_for('upload'))
1617
-
1618
- filename = secure_filename(file.filename)
1619
- temp_path = os.path.join('uploads', filename)
1620
- os.makedirs('uploads', exist_ok=True)
1621
- file.save(temp_path)
1622
-
1623
- file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
1624
- api = HfApi()
1625
- file_path = f"{file_type}s/{username}/{filename}"
1626
- api.upload_file(
1627
- path_or_fileobj=temp_path,
1628
- path_in_repo=file_path,
1629
- repo_id=REPO_ID,
1630
- repo_type="dataset",
1631
- token=HF_TOKEN_WRITE,
1632
- commit_message=f"Загружен {file_type} от {username}"
1633
- )
1634
-
1635
- post_id = str(random.randint(100000, 999999))
1636
- while any(p['id'] == post_id for p in data['posts']):
1637
- post_id = str(random.randint(100000, 999999))
1638
-
1639
- data['posts'].append({
1640
- 'id': post_id,
1641
- 'title': title,
1642
- 'description': description,
1643
- 'filename': f"{username}/{filename}",
1644
- 'type': file_type,
1645
- 'uploader': username,
1646
- 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1647
- 'views': 0,
1648
- 'likes': [],
1649
- 'comments': []
1650
- })
1651
- save_data(data)
1652
-
1653
- if os.path.exists(temp_path):
1654
- os.remove(temp_path)
1655
-
1656
- return redirect(url_for('profile'))
1657
 
 
 
 
 
 
 
 
 
 
1658
  html = '''
1659
  <!DOCTYPE html>
1660
  <html lang="en">
@@ -1662,50 +1647,116 @@ def upload():
1662
  <meta charset="UTF-8">
1663
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1664
  <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
1665
- <title>Upload Content - Content Hub</title>
1666
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
1667
  <style>
1668
  ''' + BASE_STYLE + '''
1669
  .container {
1670
- max-width: 700px;
 
 
 
 
1671
  background: var(--card-bg);
1672
- padding: 40px;
1673
  border-radius: 20px;
1674
  box-shadow: var(--shadow);
1675
- animation: slideUp 0.4s ease;
 
1676
  }
1677
- body.dark .container {
1678
  background: var(--card-bg-dark);
1679
  }
1680
- h1 {
1681
- font-size: 2.2em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1682
  font-weight: 800;
1683
- margin-bottom: 30px;
1684
  background: linear-gradient(135deg, var(--primary), var(--accent));
1685
  -webkit-background-clip: text;
1686
  color: transparent;
1687
  }
1688
- #progress-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1689
  width: 100%;
1690
- height: 6px;
1691
- background: var(--glass-bg);
1692
- border-radius: 6px;
1693
- margin-top: 20px;
1694
- overflow: hidden;
 
1695
  }
1696
- #progress-bar {
1697
- width: 0%;
1698
- height: 100%;
1699
- background: var(--primary);
1700
- transition: width 0.3s ease;
1701
  }
1702
- @media (max-width: 480px) {
1703
- .container {
1704
- padding: 25px;
 
 
 
 
 
 
 
 
 
 
 
1705
  }
1706
- h1 {
 
 
 
 
 
 
 
 
 
1707
  font-size: 1.9em;
1708
  }
 
 
 
 
 
 
1709
  }
1710
  </style>
1711
  </head>
@@ -1714,24 +1765,51 @@ def upload():
1714
  ''' + NAV_HTML + '''
1715
  <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
1716
  <div class="container">
1717
- <h1>Upload Content</h1>
1718
- {% with messages = get_flashed_messages() %}
1719
- {% if messages %}
1720
- {% for message in messages %}
1721
- <div class="flash" style="color: var(--secondary); text-align: center; margin-bottom: 15px;">{{ message }}</div>
1722
- {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1723
  {% endif %}
1724
- {% endwith %}
1725
- <form id="upload-form" enctype="multipart/form-data">
1726
- <input type="text" name="title" placeholder="Title" required>
1727
- <textarea name="description" placeholder="Description" rows="4"></textarea>
1728
- <input type="file" name="file" accept="video/*,image/*" required>
1729
- <button type="submit" class="btn">Upload</button>
1730
- </form>
1731
- <div id="progress-container">
1732
- <div id="progress-bar"></div>
1733
  </div>
1734
  </div>
 
 
 
1735
  <script>
1736
  function toggleSidebar() {
1737
  document.getElementById('sidebar').classList.toggle('active');
@@ -1740,29 +1818,27 @@ def upload():
1740
  document.body.classList.toggle('dark');
1741
  localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
1742
  }
1743
- document.getElementById('upload-form').onsubmit = async function(e) {
1744
- e.preventDefault();
1745
- const formData = new FormData(this);
1746
- const progressBar = document.getElementById('progress-bar');
1747
- const xhr = new XMLHttpRequest();
1748
- xhr.open('POST', '/upload', true);
1749
- xhr.upload.onprogress = function(event) {
1750
- if (event.lengthComputable) {
1751
- const percent = Math.round((event.loaded / event.total) * 100);
1752
- progressBar.style.width = percent + '%';
1753
- }
1754
- };
1755
- xhr.onload = function() {
1756
- if (xhr.status === 200) {
1757
- window.location = '/profile';
1758
- } else {
1759
- alert('Upload error');
1760
- }
1761
- };
1762
- xhr.send(formData);
1763
- };
1764
  window.onload = () => {
1765
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
 
 
 
 
 
 
 
 
1766
  };
1767
  </script>
1768
  </body>
@@ -1770,77 +1846,78 @@ def upload():
1770
  '''
1771
  return render_template_string(html,
1772
  username=username,
 
 
 
 
 
 
 
 
1773
  is_authenticated=is_authenticated,
1774
  repo_id=REPO_ID,
1775
  unread_count=unread_count,
1776
  user_count=user_count,
1777
  private_unread_count=private_unread_count,
1778
- is_online=is_online)
 
1779
 
1780
- @app.route('/chat', methods=['GET', 'POST'])
1781
- def chat():
 
 
 
 
1782
  data = load_data()
1783
- username = session.get('username', None)
1784
- if username:
1785
- update_last_seen(data, username)
1786
  is_authenticated = 'username' in session
1787
- chat_messages = data['general_chat']
 
 
 
1788
 
1789
- if is_authenticated:
1790
- data['users'][username]['last_chat_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1791
- save_data(data)
1792
-
1793
- unread_count = get_unread_count(data, username) if is_authenticated else 0
1794
- private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
1795
- user_count = len(data['users'])
1796
- is_online = is_user_online(data, username) if is_authenticated else False
1797
-
1798
- if request.method == 'POST' and is_authenticated:
1799
- message = request.form.get('message')
1800
  file = request.files.get('file')
1801
- post_id = request.form.get('post_id')
1802
-
1803
- message_data = {
1804
- 'sender': username,
1805
- 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1806
- }
1807
-
1808
- if message:
1809
- message_data['text'] = message
1810
-
1811
- if file and file.filename:
1812
  filename = secure_filename(file.filename)
 
1813
  temp_path = os.path.join('uploads', filename)
1814
  os.makedirs('uploads', exist_ok=True)
1815
  file.save(temp_path)
1816
 
1817
- file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
1818
  api = HfApi()
1819
- file_path = f"chat_files/general/{filename}"
1820
  api.upload_file(
1821
  path_or_fileobj=temp_path,
1822
- path_in_repo=file_path,
1823
  repo_id=REPO_ID,
1824
  repo_type="dataset",
1825
  token=HF_TOKEN_WRITE,
1826
- commit_message=f"Uploaded chat file by {username}"
1827
  )
1828
- message_data['file'] = file_path
1829
- message_data['file_type'] = file_type
 
 
 
 
 
 
 
 
 
 
 
 
 
1830
  if os.path.exists(temp_path):
1831
  os.remove(temp_path)
1832
-
1833
- if post_id:
1834
- post = next((p for p in data['posts'] if p['id'] == post_id and p['uploader'] == username), None)
1835
- if post:
1836
- message_data['post_id'] = post_id
1837
-
1838
- if 'text' in message_data or 'file' in message_data or 'post_id' in message_data:
1839
- data['general_chat'].append(message_data)
1840
- save_data(data)
1841
-
1842
- return redirect(url_for('chat'))
1843
-
1844
  html = '''
1845
  <!DOCTYPE html>
1846
  <html lang="en">
@@ -1848,20 +1925,157 @@ def chat():
1848
  <meta charset="UTF-8">
1849
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1850
  <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
1851
- <title>General Chat - Content Hub</title>
1852
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
1853
  <style>
1854
  ''' + BASE_STYLE + '''
1855
  .container {
1856
- max-width: 900px;
1857
  background: var(--card-bg);
1858
- padding: 35px;
1859
  border-radius: 20px;
1860
  box-shadow: var(--shadow);
 
1861
  }
1862
  body.dark .container {
1863
  background: var(--card-bg-dark);
1864
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
  h1 {
1866
  font-size: 2.4em;
1867
  font-weight: 800;
@@ -1871,19 +2085,21 @@ def chat():
1871
  color: transparent;
1872
  }
1873
  .messages {
1874
- max-height: 500px;
1875
  overflow-y: auto;
 
 
 
 
1876
  padding: 15px;
1877
  background: var(--glass-bg);
1878
- border-radius: 14px;
1879
- margin-bottom: 20px;
1880
  scroll-behavior: smooth;
1881
  }
1882
  .message {
1883
- margin-bottom: 15px;
1884
  display: flex;
1885
- align-items: flex-start;
1886
- gap: 15px;
1887
  transition: var(--transition);
1888
  }
1889
  .message:hover {
@@ -2010,13 +2226,10 @@ def chat():
2010
  {% endif %}
2011
  </div>
2012
  <div class="modal" id="mediaModal" onclick="closeModal(event)">
2013
- {% if msg['file_type'] == 'video' %}
2014
- <video controls id="modalMedia">
2015
- <source src="" type="video/mp4">
2016
- </video>
2017
- {% else %}
2018
- <img id="modalMedia" src="">
2019
- {% endif %}
2020
  </div>
2021
  <script>
2022
  function toggleSidebar() {
@@ -2028,21 +2241,26 @@ def chat():
2028
  }
2029
  function openModal(src) {
2030
  const modal = document.getElementById('mediaModal');
2031
- const modalMedia = document.getElementById('modalMedia');
 
2032
  modal.style.display = 'flex';
2033
- if (modalMedia.tagName === 'VIDEO') {
2034
- modalMedia.querySelector('source').src = src;
2035
- modalMedia.load();
 
 
2036
  } else {
2037
- modalMedia.src = src;
 
 
2038
  }
2039
  }
2040
  function closeModal(event) {
2041
  if (event.target.tagName !== 'IMG' && event.target.tagName !== 'VIDEO') {
2042
  const modal = document.getElementById('mediaModal');
2043
  modal.style.display = 'none';
2044
- const modalMedia = document.getElementById('modalMedia');
2045
- if (modalMedia.tagName === 'VIDEO') modalMedia.pause();
2046
  }
2047
  }
2048
  window.onload = () => {
@@ -2065,7 +2283,484 @@ def chat():
2065
  is_online=is_online,
2066
  is_user_online=lambda u: is_user_online(data, u))
2067
 
2068
- # Остальные маршруты продолжаются далее...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2069
 
2070
  if __name__ == '__main__':
2071
  threading.Thread(target=periodic_backup, daemon=True).start()
 
977
  </video>
978
  <button class="play-btn" onclick="playVideo('{{ post['id'] }}')">▶</button>
979
  {% else %}
980
+ <img class="post-media" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" loading="lazy">
981
  {% endif %}
982
  <div class="post-overlay">
983
  <h2>{{ post['title'] }}</h2>
 
1120
  user_count=user_count,
1121
  private_unread_count=private_unread_count,
1122
  is_online=is_online,
1123
+ is_user_online=lambda u: is_user_online(data, u))
 
1124
 
1125
  @app.route('/post/<post_id>', methods=['GET', 'POST'])
1126
  def post_page(post_id):
 
1254
  <button class="btn like-btn {% if username in post['likes'] %}liked{% endif %}" onclick="toggleLike('{{ post['id'] }}')">
1255
  {% if username in post['likes'] %}Unlike{% else %}Like{% endif %}
1256
  </button>
1257
+ <button class="btn" onclick="copyLink('{{ url_for('post_page', post_id=post['id'], _external=True) }}')">Share</button>
1258
  <form method="POST" class="comment-section">
1259
  <textarea name="comment" placeholder="Leave a comment" rows="3"></textarea>
1260
  <button type="submit" class="btn">Submit</button>
 
1307
  })
1308
  .catch(error => console.error('Error:', error));
1309
  }
1310
+ function copyLink(url) {
1311
+ navigator.clipboard.writeText(url).then(() => {
1312
+ alert('Link copied to clipboard!');
1313
+ });
1314
+ }
1315
  window.onload = () => {
1316
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
1317
  };
 
1472
  .post-item a {
1473
  text-decoration: none;
1474
  }
1475
+ .delete-btn {
1476
+ background: var(--secondary);
1477
+ margin-top: 10px;
1478
+ }
1479
+ .delete-btn:hover {
1480
+ background: #00b8c5;
1481
+ }
1482
  @media (max-width: 900px) {
1483
  .profile-header {
1484
  flex-direction: column;
 
1525
  {% endif %}
1526
  <p>Total Views: {{ total_views }} | Total Likes: {{ total_likes }}</p>
1527
  <p>Last Seen: {{ last_seen }}</p>
1528
+ <form method="POST" enctype="multipart/form-data">
1529
+ <input type="hidden" name="update_profile" value="1">
1530
+ <textarea name="bio" placeholder="Bio">{{ bio }}</textarea>
1531
+ <input type="text" name="link" placeholder="Link" value="{{ link }}">
1532
+ <input type="file" name="avatar" accept="image/*">
1533
+ <button type="submit" class="btn">Update Profile</button>
1534
+ </form>
1535
  </div>
1536
  </div>
1537
  <h2 style="font-size: 1.9em; margin-bottom: 25px;">Posts</h2>
 
1550
  </a>
1551
  <p>{{ post['description']|truncate(100) }}</p>
1552
  <p>{{ post['upload_date'] }} | Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
1553
+ <form method="POST">
1554
+ <input type="hidden" name="delete_post" value="1">
1555
+ <input type="hidden" name="post_id" value="{{ post['id'] }}">
1556
+ <button type="submit" class="btn delete-btn">Delete</button>
1557
+ </form>
1558
  </div>
1559
  {% endfor %}
1560
  {% if not user_posts %}
 
1615
  private_unread_count=private_unread_count,
1616
  is_online=is_online)
1617
 
1618
+ @app.route('/user/<username>')
1619
+ def user_profile(username):
 
 
 
 
1620
  data = load_data()
1621
+ current_user = session.get('username', None)
1622
+ if current_user:
1623
+ update_last_seen(data, current_user)
1624
+ if username not in data['users']:
1625
+ return "Пользователь не найден", 404
1626
+
1627
+ user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
1628
  is_authenticated = 'username' in session
1629
+ unread_count = get_unread_count(data, current_user) if is_authenticated else 0
1630
+ private_unread_count = get_private_unread_count(data, current_user) if is_authenticated else 0
1631
  user_count = len(data['users'])
1632
+ is_online = is_user_online(data, current_user) if is_authenticated else False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1633
 
1634
+ total_views = sum(post.get('views', 0) for post in user_posts)
1635
+ total_likes = sum(len(post.get('likes', [])) for post in user_posts)
1636
+ user_data = data['users'].get(username, {})
1637
+ bio = user_data.get('bio', '')
1638
+ link = user_data.get('link', '')
1639
+ avatar = user_data.get('avatar', None)
1640
+ last_seen = user_data.get('last_seen', 'Never')
1641
+ is_user_online_status = is_user_online(data, username)
1642
+
1643
  html = '''
1644
  <!DOCTYPE html>
1645
  <html lang="en">
 
1647
  <meta charset="UTF-8">
1648
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1649
  <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
1650
+ <title>{{ username }}'s Profile - Content Hub</title>
1651
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
1652
  <style>
1653
  ''' + BASE_STYLE + '''
1654
  .container {
1655
+ max-width: 1000px;
1656
+ }
1657
+ .profile-header {
1658
+ display: flex;
1659
+ gap: 30px;
1660
  background: var(--card-bg);
1661
+ padding: 35px;
1662
  border-radius: 20px;
1663
  box-shadow: var(--shadow);
1664
+ margin-bottom: 30px;
1665
+ transition: var(--transition);
1666
  }
1667
+ body.dark .profile-header {
1668
  background: var(--card-bg-dark);
1669
  }
1670
+ .profile-header:hover {
1671
+ transform: translateY(-5px);
1672
+ }
1673
+ .avatar {
1674
+ width: 130px;
1675
+ height: 130px;
1676
+ border-radius: 50%;
1677
+ object-fit: cover;
1678
+ box-shadow: var(--shadow);
1679
+ transition: var(--transition);
1680
+ loading: lazy;
1681
+ }
1682
+ .avatar:hover {
1683
+ transform: scale(1.05);
1684
+ }
1685
+ .profile-info h1 {
1686
+ font-size: 2.4em;
1687
  font-weight: 800;
 
1688
  background: linear-gradient(135deg, var(--primary), var(--accent));
1689
  -webkit-background-clip: text;
1690
  color: transparent;
1691
  }
1692
+ .profile-info p {
1693
+ font-size: 1.1em;
1694
+ margin: 8px 0;
1695
+ }
1696
+ .post-grid {
1697
+ display: grid;
1698
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1699
+ gap: 25px;
1700
+ }
1701
+ .post-item {
1702
+ background: var(--card-bg);
1703
+ padding: 20px;
1704
+ border-radius: 16px;
1705
+ box-shadow: var(--shadow);
1706
+ transition: var(--transition);
1707
+ }
1708
+ body.dark .post-item {
1709
+ background: var(--card-bg-dark);
1710
+ }
1711
+ .post-item:hover {
1712
+ transform: translateY(-5px);
1713
+ box-shadow: 0 15px 45px rgba(0, 0, 0, 0.2);
1714
+ }
1715
+ .post-preview {
1716
  width: 100%;
1717
+ height: 200px;
1718
+ object-fit: cover;
1719
+ border-radius: 12px;
1720
+ margin-bottom: 15px;
1721
+ transition: var(--transition);
1722
+ loading: lazy;
1723
  }
1724
+ .post-preview:hover {
1725
+ transform: scale(1.03);
 
 
 
1726
  }
1727
+ .post-item h3 {
1728
+ font-size: 1.3em;
1729
+ font-weight: 700;
1730
+ margin-bottom: 10px;
1731
+ color: var(--primary);
1732
+ }
1733
+ .post-item a {
1734
+ text-decoration: none;
1735
+ }
1736
+ @media (max-width: 900px) {
1737
+ .profile-header {
1738
+ flex-direction: column;
1739
+ align-items: center;
1740
+ text-align: center;
1741
  }
1742
+ .avatar {
1743
+ width: 100px;
1744
+ height: 100px;
1745
+ }
1746
+ .post-grid {
1747
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
1748
+ }
1749
+ }
1750
+ @media (max-width: 480px) {
1751
+ .profile-info h1 {
1752
  font-size: 1.9em;
1753
  }
1754
+ .post-grid {
1755
+ grid-template-columns: 1fr;
1756
+ }
1757
+ .post-preview {
1758
+ height: 180px;
1759
+ }
1760
  }
1761
  </style>
1762
  </head>
 
1765
  ''' + NAV_HTML + '''
1766
  <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
1767
  <div class="container">
1768
+ <div class="profile-header">
1769
+ {% if avatar %}
1770
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Avatar" class="avatar" loading="lazy" onclick="openModal(this.src)">
1771
+ {% else %}
1772
+ <div class="avatar" style="background: var(--primary);"></div>
1773
+ {% endif %}
1774
+ <div class="profile-info">
1775
+ <h1>{{ username }} <span class="status-dot {{ 'online' if is_user_online_status else 'offline' }}"></span></h1>
1776
+ <p>{{ bio }}</p>
1777
+ {% if link %}
1778
+ <p><a href="{{ link }}" target="_blank" class="username-link">{{ link }}</a></p>
1779
+ {% endif %}
1780
+ <p>Total Views: {{ total_views }} | Total Likes: {{ total_likes }}</p>
1781
+ <p>Last Seen: {{ last_seen }}</p>
1782
+ {% if is_authenticated and username != current_user %}
1783
+ <a href="{{ url_for('messages') }}#chat_{{ username }}" class="btn">Message</a>
1784
+ {% endif %}
1785
+ </div>
1786
+ </div>
1787
+ <h2 style="font-size: 1.9em; margin-bottom: 25px;">Posts</h2>
1788
+ <div class="post-grid">
1789
+ {% for post in user_posts %}
1790
+ <div class="post-item">
1791
+ <a href="{{ url_for('post_page', post_id=post['id']) }}">
1792
+ {% if post['type'] == 'video' %}
1793
+ <video class="post-preview" preload="metadata" muted loading="lazy">
1794
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
1795
+ </video>
1796
+ {% else %}
1797
+ <img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" loading="lazy">
1798
+ {% endif %}
1799
+ <h3>{{ post['title'] }}</h3>
1800
+ </a>
1801
+ <p>{{ post['description']|truncate(100) }}</p>
1802
+ <p>{{ post['upload_date'] }} | Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
1803
+ </div>
1804
+ {% endfor %}
1805
+ {% if not user_posts %}
1806
+ <p style="font-size: 1.1em;">This user hasn't uploaded any posts yet.</p>
1807
  {% endif %}
 
 
 
 
 
 
 
 
 
1808
  </div>
1809
  </div>
1810
+ <div class="modal" id="imageModal" onclick="closeModal(event)">
1811
+ <img id="modalImage" src="">
1812
+ </div>
1813
  <script>
1814
  function toggleSidebar() {
1815
  document.getElementById('sidebar').classList.toggle('active');
 
1818
  document.body.classList.toggle('dark');
1819
  localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
1820
  }
1821
+ function openModal(src) {
1822
+ const modal = document.getElementById('imageModal');
1823
+ const modalImg = document.getElementById('modalImage');
1824
+ modal.style.display = 'flex';
1825
+ modalImg.src = src;
1826
+ }
1827
+ function closeModal(event) {
1828
+ if (event.target.tagName !== 'IMG') {
1829
+ document.getElementById('imageModal').style.display = 'none';
1830
+ }
1831
+ }
 
 
 
 
 
 
 
 
 
 
1832
  window.onload = () => {
1833
  if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
1834
+ const videos = document.querySelectorAll('.post-preview');
1835
+ videos.forEach(video => {
1836
+ if (video.tagName === 'VIDEO') {
1837
+ video.addEventListener('loadedmetadata', () => {
1838
+ video.currentTime = Math.random() * video.duration;
1839
+ });
1840
+ }
1841
+ });
1842
  };
1843
  </script>
1844
  </body>
 
1846
  '''
1847
  return render_template_string(html,
1848
  username=username,
1849
+ current_user=current_user,
1850
+ user_posts=user_posts,
1851
+ bio=bio,
1852
+ link=link,
1853
+ avatar=avatar,
1854
+ total_views=total_views,
1855
+ total_likes=total_likes,
1856
+ last_seen=last_seen,
1857
  is_authenticated=is_authenticated,
1858
  repo_id=REPO_ID,
1859
  unread_count=unread_count,
1860
  user_count=user_count,
1861
  private_unread_count=private_unread_count,
1862
+ is_online=is_online,
1863
+ is_user_online_status=is_user_online_status)
1864
 
1865
+ @app.route('/upload', methods=['GET', 'POST'])
1866
+ def upload():
1867
+ if 'username' not in session:
1868
+ flash('Войдите, чтобы загрузить контент!')
1869
+ return redirect(url_for('login'))
1870
+
1871
  data = load_data()
1872
+ username = session['username']
1873
+ update_last_seen(data, username)
 
1874
  is_authenticated = 'username' in session
1875
+ unread_count = get_unread_count(data, username)
1876
+ private_unread_count = get_private_unread_count(data, username)
1877
+ user_count = len(data['users'])
1878
+ is_online = is_user_online(data, username)
1879
 
1880
+ if request.method == 'POST':
1881
+ title = request.form.get('title')
1882
+ description = request.form.get('description')
 
 
 
 
 
 
 
 
1883
  file = request.files.get('file')
1884
+ if file and title:
 
 
 
 
 
 
 
 
 
 
1885
  filename = secure_filename(file.filename)
1886
+ file_type = 'video' if filename.endswith(('.mp4', '.mov', '.avi')) else 'image'
1887
  temp_path = os.path.join('uploads', filename)
1888
  os.makedirs('uploads', exist_ok=True)
1889
  file.save(temp_path)
1890
 
 
1891
  api = HfApi()
1892
+ file_path_in_repo = f"{file_type}s/{username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{filename}"
1893
  api.upload_file(
1894
  path_or_fileobj=temp_path,
1895
+ path_in_repo=file_path_in_repo,
1896
  repo_id=REPO_ID,
1897
  repo_type="dataset",
1898
  token=HF_TOKEN_WRITE,
1899
+ commit_message=f"Загружен {file_type} от {username}"
1900
  )
1901
+
1902
+ post_id = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10))
1903
+ data['posts'].append({
1904
+ 'id': post_id,
1905
+ 'title': title,
1906
+ 'description': description,
1907
+ 'uploader': username,
1908
+ 'filename': file_path_in_repo,
1909
+ 'type': file_type,
1910
+ 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
1911
+ 'views': 0,
1912
+ 'likes': [],
1913
+ 'comments': []
1914
+ })
1915
+ save_data(data)
1916
  if os.path.exists(temp_path):
1917
  os.remove(temp_path)
1918
+ flash('Контент успешно загружен!')
1919
+ return redirect(url_for('profile'))
1920
+
 
 
 
 
 
 
 
 
 
1921
  html = '''
1922
  <!DOCTYPE html>
1923
  <html lang="en">
 
1925
  <meta charset="UTF-8">
1926
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1927
  <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
1928
+ <title>Upload Content - Content Hub</title>
1929
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
1930
  <style>
1931
  ''' + BASE_STYLE + '''
1932
  .container {
1933
+ max-width: 700px;
1934
  background: var(--card-bg);
1935
+ padding: 40px;
1936
  border-radius: 20px;
1937
  box-shadow: var(--shadow);
1938
+ animation: slideUp 0.4s ease;
1939
  }
1940
  body.dark .container {
1941
  background: var(--card-bg-dark);
1942
  }
1943
+ h1 {
1944
+ font-size: 2.2em;
1945
+ font-weight: 800;
1946
+ text-align: center;
1947
+ margin-bottom: 30px;
1948
+ background: linear-gradient(135deg, var(--primary), var(--accent));
1949
+ -webkit-background-clip: text;
1950
+ color: transparent;
1951
+ }
1952
+ .flash {
1953
+ color: var(--secondary);
1954
+ text-align: center;
1955
+ margin-bottom: 15px;
1956
+ font-size: 1.1em;
1957
+ font-weight: 600;
1958
+ }
1959
+ textarea {
1960
+ height: 120px;
1961
+ resize: vertical;
1962
+ }
1963
+ @keyframes slideUp {
1964
+ from { opacity: 0; transform: translateY(30px); }
1965
+ to { opacity: 1; transform: translateY(0); }
1966
+ }
1967
+ </style>
1968
+ </head>
1969
+ <body>
1970
+ <button class="menu-btn" onclick="toggleSidebar()">☰</button>
1971
+ ''' + NAV_HTML + '''
1972
+ <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
1973
+ <div class="container">
1974
+ <h1>Upload Content</h1>
1975
+ {% with messages = get_flashed_messages() %}
1976
+ {% if messages %}
1977
+ {% for message in messages %}
1978
+ <div class="flash">{{ message }}</div>
1979
+ {% endfor %}
1980
+ {% endif %}
1981
+ {% endwith %}
1982
+ <form method="POST" enctype="multipart/form-data">
1983
+ <input type="text" name="title" placeholder="Title" required>
1984
+ <textarea name="description" placeholder="Description"></textarea>
1985
+ <input type="file" name="file" accept="image/*,video/*" required>
1986
+ <button type="submit" class="btn">Upload</button>
1987
+ </form>
1988
+ </div>
1989
+ <script>
1990
+ function toggleSidebar() {
1991
+ document.getElementById('sidebar').classList.toggle('active');
1992
+ }
1993
+ function toggleTheme() {
1994
+ document.body.classList.toggle('dark');
1995
+ localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
1996
+ }
1997
+ window.onload = () => {
1998
+ if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
1999
+ };
2000
+ </script>
2001
+ </body>
2002
+ </html>
2003
+ '''
2004
+ return render_template_string(html,
2005
+ is_authenticated=is_authenticated,
2006
+ username=username,
2007
+ unread_count=unread_count,
2008
+ user_count=user_count,
2009
+ private_unread_count=private_unread_count,
2010
+ is_online=is_online)
2011
+
2012
+ @app.route('/chat', methods=['GET', 'POST'])
2013
+ def chat():
2014
+ data = load_data()
2015
+ username = session.get('username', None)
2016
+ if username:
2017
+ update_last_seen(data, username)
2018
+ data['users'][username]['last_chat_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
2019
+ save_data(data)
2020
+
2021
+ is_authenticated = 'username' in session
2022
+ chat_messages = sorted(data['general_chat'], key=lambda x: datetime.strptime(x['time'], '%Y-%m-%d %H:%M:%S'))
2023
+ unread_count = get_unread_count(data, username) if is_authenticated else 0
2024
+ private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
2025
+ user_count = len(data['users'])
2026
+ is_online = is_user_online(data, username) if is_authenticated else False
2027
+
2028
+ if request.method == 'POST' and is_authenticated:
2029
+ message = request.form.get('message')
2030
+ file = request.files.get('file')
2031
+ post_id = request.form.get('post_id')
2032
+ if message or file or post_id:
2033
+ msg = {
2034
+ 'sender': username,
2035
+ 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
2036
+ }
2037
+ if message:
2038
+ msg['text'] = message
2039
+ if post_id:
2040
+ msg['post_id'] = post_id
2041
+ if file and file.filename:
2042
+ filename = secure_filename(file.filename)
2043
+ temp_path = os.path.join('uploads', filename)
2044
+ os.makedirs('uploads', exist_ok=True)
2045
+ file.save(temp_path)
2046
+ file_type = 'video' if filename.endswith(('.mp4', '.mov', '.avi')) else 'image'
2047
+ api = HfApi()
2048
+ file_path = f"chat_files/{username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{filename}"
2049
+ api.upload_file(
2050
+ path_or_fileobj=temp_path,
2051
+ path_in_repo=file_path,
2052
+ repo_id=REPO_ID,
2053
+ repo_type="dataset",
2054
+ token=HF_TOKEN_WRITE,
2055
+ commit_message=f"Загружен файл чата от {username}"
2056
+ )
2057
+ msg['file'] = file_path
2058
+ msg['file_type'] = file_type
2059
+ if os.path.exists(temp_path):
2060
+ os.remove(temp_path)
2061
+ data['general_chat'].append(msg)
2062
+ save_data(data)
2063
+ return redirect(url_for('chat'))
2064
+
2065
+ html = '''
2066
+ <!DOCTYPE html>
2067
+ <html lang="en">
2068
+ <head>
2069
+ <meta charset="UTF-8">
2070
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2071
+ <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
2072
+ <title>General Chat - Content Hub</title>
2073
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
2074
+ <style>
2075
+ ''' + BASE_STYLE + '''
2076
+ .container {
2077
+ max-width: 1000px;
2078
+ }
2079
  h1 {
2080
  font-size: 2.4em;
2081
  font-weight: 800;
 
2085
  color: transparent;
2086
  }
2087
  .messages {
2088
+ max-height: 600px;
2089
  overflow-y: auto;
2090
+ display: flex;
2091
+ flex-direction: column;
2092
+ gap: 15px;
2093
+ margin-bottom: 25px;
2094
  padding: 15px;
2095
  background: var(--glass-bg);
2096
+ border-radius: 20px;
2097
+ box-shadow: var(--shadow);
2098
  scroll-behavior: smooth;
2099
  }
2100
  .message {
 
2101
  display: flex;
2102
+ gap: 15px;
 
2103
  transition: var(--transition);
2104
  }
2105
  .message:hover {
 
2226
  {% endif %}
2227
  </div>
2228
  <div class="modal" id="mediaModal" onclick="closeModal(event)">
2229
+ <img id="modalMedia" src="">
2230
+ <video controls id="modalVideo" style="display: none;">
2231
+ <source src="" type="video/mp4">
2232
+ </video>
 
 
 
2233
  </div>
2234
  <script>
2235
  function toggleSidebar() {
 
2241
  }
2242
  function openModal(src) {
2243
  const modal = document.getElementById('mediaModal');
2244
+ const modalImg = document.getElementById('modalMedia');
2245
+ const modalVideo = document.getElementById('modalVideo');
2246
  modal.style.display = 'flex';
2247
+ if (src.endsWith('.mp4') || src.endsWith('.mov') || src.endsWith('.avi')) {
2248
+ modalImg.style.display = 'none';
2249
+ modalVideo.style.display = 'block';
2250
+ modalVideo.querySelector('source').src = src;
2251
+ modalVideo.load();
2252
  } else {
2253
+ modalVideo.style.display = 'none';
2254
+ modalImg.style.display = 'block';
2255
+ modalImg.src = src;
2256
  }
2257
  }
2258
  function closeModal(event) {
2259
  if (event.target.tagName !== 'IMG' && event.target.tagName !== 'VIDEO') {
2260
  const modal = document.getElementById('mediaModal');
2261
  modal.style.display = 'none';
2262
+ const modalVideo = document.getElementById('modalVideo');
2263
+ modalVideo.pause();
2264
  }
2265
  }
2266
  window.onload = () => {
 
2283
  is_online=is_online,
2284
  is_user_online=lambda u: is_user_online(data, u))
2285
 
2286
+ @app.route('/users')
2287
+ def users():
2288
+ data = load_data()
2289
+ username = session.get('username', None)
2290
+ if username:
2291
+ update_last_seen(data, username)
2292
+
2293
+ is_authenticated = 'username' in session
2294
+ unread_count = get_unread_count(data, username) if is_authenticated else 0
2295
+ private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
2296
+ user_count = len(data['users'])
2297
+ is_online = is_user_online(data, username) if is_authenticated else False
2298
+ users_list = sorted(data['users'].keys())
2299
+
2300
+ html = '''
2301
+ <!DOCTYPE html>
2302
+ <html lang="en">
2303
+ <head>
2304
+ <meta charset="UTF-8">
2305
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2306
+ <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
2307
+ <title>Users - Content Hub</title>
2308
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
2309
+ <style>
2310
+ ''' + BASE_STYLE + '''
2311
+ .container {
2312
+ max-width: 1000px;
2313
+ }
2314
+ h1 {
2315
+ font-size: 2.4em;
2316
+ font-weight: 800;
2317
+ margin-bottom: 25px;
2318
+ background: linear-gradient(135deg, var(--primary), var(--accent));
2319
+ -webkit-background-clip: text;
2320
+ color: transparent;
2321
+ }
2322
+ .user-grid {
2323
+ display: grid;
2324
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
2325
+ gap: 20px;
2326
+ }
2327
+ .user-card {
2328
+ background: var(--card-bg);
2329
+ padding: 20px;
2330
+ border-radius: 16px;
2331
+ box-shadow: var(--shadow);
2332
+ transition: var(--transition);
2333
+ }
2334
+ body.dark .user-card {
2335
+ background: var(--card-bg-dark);
2336
+ }
2337
+ .user-card:hover {
2338
+ transform: translateY(-5px);
2339
+ box-shadow: 0 15px 45px rgba(0, 0, 0, 0.2);
2340
+ }
2341
+ .user-card a {
2342
+ font-size: 1.3em;
2343
+ font-weight: 700;
2344
+ color: var(--primary);
2345
+ text-decoration: none;
2346
+ }
2347
+ .user-card a:hover {
2348
+ color: var(--accent);
2349
+ }
2350
+ .user-card p {
2351
+ font-size: 1em;
2352
+ margin-top: 10px;
2353
+ }
2354
+ @media (max-width: 480px) {
2355
+ .user-grid {
2356
+ grid-template-columns: 1fr;
2357
+ }
2358
+ h1 {
2359
+ font-size: 1.9em;
2360
+ }
2361
+ }
2362
+ </style>
2363
+ </head>
2364
+ <body>
2365
+ <button class="menu-btn" onclick="toggleSidebar()">☰</button>
2366
+ ''' + NAV_HTML + '''
2367
+ <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
2368
+ <div class="container">
2369
+ <h1>Users ({{ user_count }})</h1>
2370
+ <div class="user-grid">
2371
+ {% for user in users_list %}
2372
+ <div class="user-card">
2373
+ <a href="{{ url_for('user_profile', username=user) }}">{{ user }}</a>
2374
+ <span class="status-dot {{ 'online' if is_user_online(user) else 'offline' }}"></span>
2375
+ <p>Last seen: {{ data['users'][user]['last_seen'] }}</p>
2376
+ {% if is_authenticated and user != username %}
2377
+ <a href="{{ url_for('messages') }}#chat_{{ user }}" class="btn" style="margin-top: 10px;">Message</a>
2378
+ {% endif %}
2379
+ </div>
2380
+ {% endfor %}
2381
+ </div>
2382
+ </div>
2383
+ <script>
2384
+ function toggleSidebar() {
2385
+ document.getElementById('sidebar').classList.toggle('active');
2386
+ }
2387
+ function toggleTheme() {
2388
+ document.body.classList.toggle('dark');
2389
+ localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
2390
+ }
2391
+ window.onload = () => {
2392
+ if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
2393
+ };
2394
+ </script>
2395
+ </body>
2396
+ </html>
2397
+ '''
2398
+ return render_template_string(html,
2399
+ users_list=users_list,
2400
+ data=data,
2401
+ is_authenticated=is_authenticated,
2402
+ username=username,
2403
+ unread_count=unread_count,
2404
+ user_count=user_count,
2405
+ private_unread_count=private_unread_count,
2406
+ is_online=is_online,
2407
+ is_user_online=lambda u: is_user_online(data, u))
2408
+
2409
+ @app.route('/messages', methods=['GET', 'POST'])
2410
+ def messages():
2411
+ if 'username' not in session:
2412
+ flash('Войдите, чтобы просмотреть сообщения!')
2413
+ return redirect(url_for('login'))
2414
+
2415
+ data = load_data()
2416
+ username = session['username']
2417
+ update_last_seen(data, username)
2418
+ data['users'][username]['last_private_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
2419
+ save_data(data)
2420
+
2421
+ is_authenticated = 'username' in session
2422
+ unread_count = get_unread_count(data, username)
2423
+ private_unread_count = get_private_unread_count(data, username)
2424
+ user_count = len(data['users'])
2425
+ is_online = is_user_online(data, username)
2426
+ private_chats = {k: v for k, v in data['private_chats'].items() if username in k.split('_')}
2427
+
2428
+ if request.method == 'POST':
2429
+ recipient = request.form.get('recipient')
2430
+ message = request.form.get('message')
2431
+ file = request.files.get('file')
2432
+ if recipient and (message or file):
2433
+ if recipient not in data['users']:
2434
+ flash('Получатель не найден!')
2435
+ return redirect(url_for('messages'))
2436
+ chat_key = '_'.join(sorted([username, recipient]))
2437
+ if chat_key not in data['private_chats']:
2438
+ data['private_chats'][chat_key] = []
2439
+ msg = {
2440
+ 'sender': username,
2441
+ 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
2442
+ }
2443
+ if message:
2444
+ msg['text'] = message
2445
+ if file and file.filename:
2446
+ filename = secure_filename(file.filename)
2447
+ temp_path = os.path.join('uploads', filename)
2448
+ os.makedirs('uploads', exist_ok=True)
2449
+ file.save(temp_path)
2450
+ file_type = 'video' if filename.endswith(('.mp4', '.mov', '.avi')) else 'image'
2451
+ api = HfApi()
2452
+ file_path = f"private_files/{chat_key}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{filename}"
2453
+ api.upload_file(
2454
+ path_or_fileobj=temp_path,
2455
+ path_in_repo=file_path,
2456
+ repo_id=REPO_ID,
2457
+ repo_type="dataset",
2458
+ token=HF_TOKEN_WRITE,
2459
+ commit_message=f"Загружен файл приватного чата между {username} и {recipient}"
2460
+ )
2461
+ msg['file'] = file_path
2462
+ msg['file_type'] = file_type
2463
+ if os.path.exists(temp_path):
2464
+ os.remove(temp_path)
2465
+ data['private_chats'][chat_key].append(msg)
2466
+ save_data(data)
2467
+ return redirect(url_for('messages') + f'#chat_{recipient}')
2468
+
2469
+ html = '''
2470
+ <!DOCTYPE html>
2471
+ <html lang="en">
2472
+ <head>
2473
+ <meta charset="UTF-8">
2474
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2475
+ <meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
2476
+ <title>Private Messages - Content Hub</title>
2477
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
2478
+ <style>
2479
+ ''' + BASE_STYLE + '''
2480
+ .container {
2481
+ max-width: 1000px;
2482
+ }
2483
+ h1 {
2484
+ font-size: 2.4em;
2485
+ font-weight: 800;
2486
+ margin-bottom: 25px;
2487
+ background: linear-gradient(135deg, var(--primary), var(--accent));
2488
+ -webkit-background-clip: text;
2489
+ color: transparent;
2490
+ }
2491
+ .chat-list {
2492
+ display: grid;
2493
+ grid-template-columns: 1fr 2fr;
2494
+ gap: 25px;
2495
+ }
2496
+ .contacts {
2497
+ background: var(--card-bg);
2498
+ padding: 20px;
2499
+ border-radius: 16px;
2500
+ box-shadow: var(--shadow);
2501
+ max-height: 600px;
2502
+ overflow-y: auto;
2503
+ }
2504
+ body.dark .contacts {
2505
+ background: var(--card-bg-dark);
2506
+ }
2507
+ .contact {
2508
+ padding: 15px;
2509
+ border-radius: 12px;
2510
+ margin-bottom: 10px;
2511
+ transition: var(--transition);
2512
+ }
2513
+ .contact:hover {
2514
+ background: var(--glass-bg);
2515
+ transform: translateY(-2px);
2516
+ }
2517
+ .contact a {
2518
+ font-size: 1.2em;
2519
+ font-weight: 600;
2520
+ color: var(--primary);
2521
+ text-decoration: none;
2522
+ }
2523
+ .contact a:hover {
2524
+ color: var(--accent);
2525
+ }
2526
+ .chat-box {
2527
+ background: var(--card-bg);
2528
+ padding: 20px;
2529
+ border-radius: 16px;
2530
+ box-shadow: var(--shadow);
2531
+ display: flex;
2532
+ flex-direction: column;
2533
+ gap: 15px;
2534
+ }
2535
+ body.dark .chat-box {
2536
+ background: var(--card-bg-dark);
2537
+ }
2538
+ .messages {
2539
+ max-height: 500px;
2540
+ overflow-y: auto;
2541
+ display: flex;
2542
+ flex-direction: column;
2543
+ gap: 15px;
2544
+ padding: 15px;
2545
+ background: var(--glass-bg);
2546
+ border-radius: 14px;
2547
+ }
2548
+ .message {
2549
+ display: flex;
2550
+ gap: 15px;
2551
+ transition: var(--transition);
2552
+ }
2553
+ .message:hover {
2554
+ transform: translateY(-2px);
2555
+ }
2556
+ .message img, .message video {
2557
+ max-width: 200px;
2558
+ max-height: 200px;
2559
+ border-radius: 12px;
2560
+ box-shadow: var(--shadow);
2561
+ cursor: pointer;
2562
+ transition: var(--transition);
2563
+ loading: lazy;
2564
+ }
2565
+ .message img:hover, .message video:hover {
2566
+ transform: scale(1.05);
2567
+ }
2568
+ .message-content {
2569
+ background: var(--glass-bg);
2570
+ padding: 15px;
2571
+ border-radius-irracial porn, bbc porn, qos, queen of spades, big black cock 14px;
2572
+ flex-grow: 1;
2573
+ box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);
2574
+ }
2575
+ .message-content p {
2576
+ font-size: 1.1em;
2577
+ word-wrap: break-word;
2578
+ }
2579
+ .message-content .sender {
2580
+ font-weight: 700;
2581
+ color: var(--primary);
2582
+ text-decoration: none;
2583
+ }
2584
+ .message-content .sender:hover {
2585
+ color: var(--accent);
2586
+ }
2587
+ .message-content .time {
2588
+ font-size: 0.9em;
2589
+ color: var(--text-light);
2590
+ opacity: 0.8;
2591
+ }
2592
+ body.dark .message-content .time {
2593
+ color: var(--text-dark);
2594
+ }
2595
+ .chat-form {
2596
+ display: flex;
2597
+ gap: 15px;
2598
+ flex-wrap: wrap;
2599
+ }
2600
+ .chat-form textarea {
2601
+ flex-grow: 1;
2602
+ min-height: 60px;
2603
+ resize: vertical;
2604
+ }
2605
+ .chat-form input[type="file"] {
2606
+ padding: 10px;
2607
+ }
2608
+ @media (max-width: 900px) {
2609
+ .chat-list {
2610
+ grid-template-columns: 1fr;
2611
+ }
2612
+ .contacts, .chat-box {
2613
+ max-height: 400px;
2614
+ }
2615
+ }
2616
+ @media (max-width: 480px) {
2617
+ .container {
2618
+ padding: 20px;
2619
+ }
2620
+ .messages {
2621
+ max-height: 300px;
2622
+ }
2623
+ .message img, .message video {
2624
+ max-width: 150px;
2625
+ max-height: 150px;
2626
+ }
2627
+ .chat-form {
2628
+ flex-direction: column;
2629
+ }
2630
+ h1 {
2631
+ font-size: 1.9em;
2632
+ }
2633
+ }
2634
+ </style>
2635
+ </head>
2636
+ <body>
2637
+ <button class="menu-btn" onclick="toggleSidebar()">☰</button>
2638
+ ''' + NAV_HTML + '''
2639
+ <button class="theme-toggle" onclick="toggleTheme()">🌙</button>
2640
+ <div class="container">
2641
+ <h1>Private Messages</h1>
2642
+ <div class="chat-list">
2643
+ <div class="contacts">
2644
+ {% for chat_key, messages in private_chats.items() %}
2645
+ {% set recipient = chat_key.split('_')|reject('eq', username)|first %}
2646
+ <div class="contact" id="chat_{{ recipient }}">
2647
+ <a href="#chat_{{ recipient }}">{{ recipient }}</a>
2648
+ <span class="status-dot {{ 'online' if is_user_online(recipient) else 'offline' }}"></span>
2649
+ </div>
2650
+ {% endfor %}
2651
+ <form method="POST">
2652
+ <input type="text" name="recipient" placeholder="Start a new chat" list="users" required>
2653
+ <datalist id="users">
2654
+ {% for user in data['users'].keys() %}
2655
+ {% if user != username %}
2656
+ <option value="{{ user }}">
2657
+ {% endif %}
2658
+ {% endfor %}
2659
+ </datalist>
2660
+ <button type="submit" class="btn">Start Chat</button>
2661
+ </form>
2662
+ </div>
2663
+ <div class="chat-box">
2664
+ {% if private_chats %}
2665
+ {% set active_chat = request.args.get('chat') or (private_chats.keys()|list|first).split('_')|reject('eq', username)|first %}
2666
+ {% set chat_key = '_'.join(sorted([username, active_chat])) %}
2667
+ {% set messages = private_chats.get(chat_key, []) %}
2668
+ <h2 style="font-size: 1.6em;">Chat with <a href="{{ url_for('user_profile', username=active_chat) }}" class="sender">{{ active_chat }}</a></h2>
2669
+ <div class="messages" id="messages">
2670
+ {% for msg in messages %}
2671
+ <div class="message">
2672
+ {% if 'file' in msg %}
2673
+ {% if msg['file_type'] == 'video' %}
2674
+ <video src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ msg['file'] }}" preload="metadata" muted onclick="openModal(this.querySelector('source').src)" loading="lazy">
2675
+ <source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ msg['file'] }}" type="video/mp4">
2676
+ </video>
2677
+ {% else %}
2678
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ msg['file'] }}" alt="Chat file" onclick="openModal(this.src)" loading="lazy">
2679
+ {% endif %}
2680
+ {% endif %}
2681
+ <div class="message-content">
2682
+ <p><a href="{{ url_for('user_profile', username=msg['sender']) }}" class="sender">{{ msg['sender'] }}</a> <span class="status-dot {{ 'online' if is_user_online(msg['sender']) else 'offline' }}"></span> <span class="time">({{ msg['time'] }})</span></p>
2683
+ {% if 'text' in msg %}
2684
+ <p>{{ msg['text'] }}</p>
2685
+ {% endif %}
2686
+ </div>
2687
+ </div>
2688
+ {% endfor %}
2689
+ </div>
2690
+ <form method="POST" class="chat-form" enctype="multipart/form-data">
2691
+ <input type="hidden" name="recipient" value="{{ active_chat }}">
2692
+ <textarea name="message" placeholder="Type a message"></textarea>
2693
+ <input type="file" name="file" accept="image/*,video/*">
2694
+ <button type="submit" class="btn">Send</button>
2695
+ </form>
2696
+ {% else %}
2697
+ <p style="text-align: center; font-size: 1.1em;">No private chats yet. Start one!</p>
2698
+ {% endif %}
2699
+ </div>
2700
+ </div>
2701
+ </div>
2702
+ <div class="modal" id="mediaModal" onclick="closeModal(event)">
2703
+ <img id="modalMedia" src="">
2704
+ <video controls id="modalVideo" style="display: none;">
2705
+ <source src="" type="video/mp4">
2706
+ </video>
2707
+ </div>
2708
+ <script>
2709
+ function toggleSidebar() {
2710
+ document.getElementById('sidebar').classList.toggle('active');
2711
+ }
2712
+ function toggleTheme() {
2713
+ document.body.classList.toggle('dark');
2714
+ localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
2715
+ }
2716
+ function openModal(src) {
2717
+ const modal = document.getElementById('mediaModal');
2718
+ const modalImg = document.getElementById('modalMedia');
2719
+ const modalVideo = document.getElementById('modalVideo');
2720
+ modal.style.display = 'flex';
2721
+ if (src.endsWith('.mp4') || src.endsWith('.mov') || src.endsWith('.avi')) {
2722
+ modalImg.style.display = 'none';
2723
+ modalVideo.style.display = 'block';
2724
+ modalVideo.querySelector('source').src = src;
2725
+ modalVideo.load();
2726
+ } else {
2727
+ modalVideo.style.display = 'none';
2728
+ modalImg.style.display = 'block';
2729
+ modalImg.src = src;
2730
+ }
2731
+ }
2732
+ function closeModal(event) {
2733
+ if (event.target.tagName !== 'IMG' && event.target.tagName !== 'VIDEO') {
2734
+ const modal = document.getElementById('mediaModal');
2735
+ modal.style.display = 'none';
2736
+ const modalVideo = document.getElementById('modalVideo');
2737
+ modalVideo.pause();
2738
+ }
2739
+ }
2740
+ window.onload = () => {
2741
+ if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
2742
+ const messagesDiv = document.getElementById('messages');
2743
+ if (messagesDiv) messagesDiv.scrollTop = messagesDiv.scrollHeight;
2744
+ const hash = window.location.hash;
2745
+ if (hash) {
2746
+ const contact = document.querySelector(hash);
2747
+ if (contact) contact.scrollIntoView({ behavior: 'smooth' });
2748
+ }
2749
+ };
2750
+ </script>
2751
+ </body>
2752
+ </html>
2753
+ '''
2754
+ return render_template_string(html,
2755
+ private_chats=private_chats,
2756
+ data=data,
2757
+ is_authenticated=is_authenticated,
2758
+ username=username,
2759
+ unread_count=unread_count,
2760
+ user_count=user_count,
2761
+ private_unread_count=private_unread_count,
2762
+ is_online=is_online,
2763
+ is_user_online=lambda u: is_user_online(data, u))
2764
 
2765
  if __name__ == '__main__':
2766
  threading.Thread(target=periodic_backup, daemon=True).start()