From 1fd4c94097c9f910e881b8397b2809a295448722 Mon Sep 17 00:00:00 2001 From: gz Date: Fri, 29 May 2026 09:40:07 +0800 Subject: [PATCH] Initial commit --- README.md | 0 __pycache__/chat.cpython-312.pyc | Bin 0 -> 1899 bytes __pycache__/chat.cpython-38.pyc | Bin 0 -> 8853 bytes backups/chat_history_20260522_143254.txt | 99 +++ backups/chat_history_20260522_143305.txt | 2 + backups/chat_history_20260523_030001.txt | 131 ++++ backups/chat_history_20260524_030001.txt | 8 + backups/chat_history_20260525_030001.txt | 6 + backups/chat_history_20260526_030001.txt | 20 + backups/chat_history_20260527_030001.txt | 26 + backups/chat_history_20260528_030001.txt | 39 ++ backups/chat_history_20260529_030001.txt | 21 + chat.py | 300 +++++++++ chat.py_20260506 | 76 +++ chat.py_new | 160 +++++ chat_history.txt | 0 nightly_cleanup.log | 21 + nightly_cleanup.sh | 15 + requirements.txt | 3 + templates/chat.html | 821 +++++++++++++++++++++++ templates/chat.html_ | 199 ++++++ templates/chat.html_20260506 | 354 ++++++++++ templates/chat.html_bak | 140 ++++ templates/chat.html_bak2 | 246 +++++++ templates/chat.html_bak3 | 246 +++++++ templates/chat.html_new | 234 +++++++ uwsgi.ini | 30 + 27 files changed, 3197 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/chat.cpython-312.pyc create mode 100644 __pycache__/chat.cpython-38.pyc create mode 100644 backups/chat_history_20260522_143254.txt create mode 100644 backups/chat_history_20260522_143305.txt create mode 100644 backups/chat_history_20260523_030001.txt create mode 100644 backups/chat_history_20260524_030001.txt create mode 100644 backups/chat_history_20260525_030001.txt create mode 100644 backups/chat_history_20260526_030001.txt create mode 100644 backups/chat_history_20260527_030001.txt create mode 100644 backups/chat_history_20260528_030001.txt create mode 100644 backups/chat_history_20260529_030001.txt create mode 100644 chat.py create mode 100644 chat.py_20260506 create mode 100644 chat.py_new create mode 100644 chat_history.txt create mode 100644 nightly_cleanup.log create mode 100755 nightly_cleanup.sh create mode 100644 requirements.txt create mode 100644 templates/chat.html create mode 100644 templates/chat.html_ create mode 100644 templates/chat.html_20260506 create mode 100644 templates/chat.html_bak create mode 100644 templates/chat.html_bak2 create mode 100644 templates/chat.html_bak3 create mode 100644 templates/chat.html_new create mode 100644 uwsgi.ini diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__pycache__/chat.cpython-312.pyc b/__pycache__/chat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17799fe17890385a11762421956efd334e8d794f GIT binary patch literal 1899 zcmZWpO>7fK6n?Y2{#pMd#!d_lPMQ=@acHs$6e>YmH8kRmb{fp~)%jMc z%1tZPNBx%s)&G^W?J0zN_Ox2pu^QOk6Qly}X=~XNrV+J$YCiyrVjO`iq8Eo6Qg^7~ zJCS9~5FZ3e?vgvv@fT+jqU|}6Hm#BI)3*F6y?D;ZOi{~@Y{OB_%ozDR%~%Oy2Tq;; z@Po6|EEsu{BJV$Er7bOULd#JT+4lFfd^UaXJk<-iwB?9AXXzKy)U?KI*=iBXs>aZz z!AehwVtZKKDAULw4-9zy=5cNvMREqCUIYtKePkPFY4Utki7L#3MwW%T^c;LI7D+-J zE6bV5v^6+s={XieT|!{IZR}7p&}S!d^d=9Z_vw4e$0lW_Ql2O)qef<0r+Ld%R63)K zm(U^8$C;7ELzqgTWKA0R2CML&Ib^UePB{Ls6nMcsfec^B*6uaIZnN8$6` z09S-rAuGS^VU{%ivmIf>2Z0;i65M$EO}OzH&I368d%$w;JNL5}*skYJ!7Ka1Xg$RR zC_xb}ix;3soW3Aq36JegXEL;4+2M4?(q?F?(druZH|h!nnpf=rb}Hpci(a;D*+iKG z8PA*TW>hz3XhLLR&Pr<9O545`KfF_WdwW@ktHay%Ore{Cq2(6yK1dRjIoob$_efm-g2Lw`*X%ExaCkwHi|@F=Z__T#XDbjBfM{ zJpvLOzUf8(qatJ4;>?iVZ}v8hNTsEAIGne zkwfm4{T_}Lf+j1&gyTaFJ=}9VD&C6-D8JW9IF5>lZEq^2r?q@4Rrc~*a}+aP_6!X= zpZ4x6{Hog0q+wdNR4|y8=(IhT@Uco4G{)1)_&unXVMjwg$QrMc$=s;4BTYH(!k6k2 zf2kO6Wjox|G~OjGKf%0Q5o#(Luae1lwP3Yf*1Di0o344Em zw%^>rd2LSo#|^>w($xLH!8y6+hUl(U2;(ZauJ63Mb0IPxuL*>lCO3;%LtM8+5ZdY@ ztT0-2U+rF)UL&0~K_&yYY1CqrP$q$2hx$6 L2vQg8#~}X)Sqi=L literal 0 HcmV?d00001 diff --git a/__pycache__/chat.cpython-38.pyc b/__pycache__/chat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c56ff1f133dda9c51c628c389574f409d12d759a GIT binary patch literal 8853 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 literal 0 HcmV?d00001 diff --git a/backups/chat_history_20260522_143254.txt b/backups/chat_history_20260522_143254.txt new file mode 100644 index 0000000..deb880d --- /dev/null +++ b/backups/chat_history_20260522_143254.txt @@ -0,0 +1,99 @@ +[2026-05-22 12:59:36] SYSTEM::zgz 加入聊天室 +[2026-05-22 12:59:38] SYSTEM::gz 加入聊天室 +[2026-05-22 13:00:32] SYSTEM::gz 离开聊天室 +[2026-05-22 13:00:32] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:00:36] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:00:37] SYSTEM::gz 加入聊天室 +[2026-05-22 13:01:06] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:01:09] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:01:15] TEXT::zgz::?试一下 +[2026-05-22 13:01:18] TEXT::zgz::???? +[2026-05-22 13:01:27] TEXT::zgz::123123123123、 +[2026-05-22 13:05:09] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:05:09] SYSTEM::gz 离开聊天室 +[2026-05-22 13:05:14] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:05:14] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:05:18] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:05:21] SYSTEM::gz 加入聊天室 +[2026-05-22 13:12:59] SYSTEM::gz 离开聊天室 +[2026-05-22 13:12:59] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:13:04] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:13:05] SYSTEM::gz 加入聊天室 +[2026-05-22 13:16:58] SYSTEM::gz 离开聊天室 +[2026-05-22 13:16:58] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:17:03] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:17:04] SYSTEM::gz 加入聊天室 +[2026-05-22 13:18:06] SYSTEM::gz 离开聊天室 +[2026-05-22 13:18:06] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:18:12] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:18:14] SYSTEM::gz 加入聊天室 +[2026-05-22 13:19:14] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:19:17] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:19:18] TEXT::zgz::? +[2026-05-22 13:19:23] TEXT::zgz::你好 +[2026-05-22 13:19:26] TEXT::zgz::你好 +[2026-05-22 13:19:27] TEXT::zgz::显示 +[2026-05-22 13:19:31] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:19:33] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:19:35] TEXT::zgz::你好 +[2026-05-22 13:20:14] TEXT::zgz::??? +[2026-05-22 13:20:19] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:20:21] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:20:22] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:20:27] SYSTEM::gz 离开聊天室 +[2026-05-22 13:20:30] SYSTEM::gz 加入聊天室 +[2026-05-22 13:20:35] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:20:45] TEXT::gz::你好 +[2026-05-22 13:20:48] TEXT::gz::??? +[2026-05-22 13:20:55] TEXT::zgz::???? +[2026-05-22 13:28:15] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:28:18] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:28:19] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:28:21] SYSTEM::zgz 加入聊天室 +[2026-05-22 13:30:49] SYSTEM::zgz 离开聊天室 +[2026-05-22 13:30:49] SYSTEM::gz 离开聊天室 +[2026-05-22 14:04:27] SYSTEM::gz 加入聊天室 +[2026-05-22 14:04:31] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:04:32] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:04:34] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:04:37] TEXT::zgz::123 +[2026-05-22 14:04:38] TEXT::zgz::123 +[2026-05-22 14:04:38] TEXT::zgz::12 +[2026-05-22 14:04:38] TEXT::zgz::3 +[2026-05-22 14:04:38] TEXT::zgz::21 +[2026-05-22 14:04:38] TEXT::zgz::321 +[2026-05-22 14:04:39] TEXT::zgz::31 +[2026-05-22 14:04:41] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:04:43] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:04:52] TEXT::zgz::12313 +[2026-05-22 14:04:54] TEXT::zgz::1111 +[2026-05-22 14:11:15] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:11:17] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:11:19] TEXT::zgz::123 +[2026-05-22 14:11:20] TEXT::zgz::123 +[2026-05-22 14:11:22] TEXT::zgz::qwe +[2026-05-22 14:11:22] TEXT::zgz::qwe +[2026-05-22 14:11:25] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:11:27] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:12:30] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:12:30] SYSTEM::gz 离开聊天室 +[2026-05-22 14:13:41] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:13:42] SYSTEM::gz 加入聊天室 +[2026-05-22 14:13:55] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:13:58] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:13:58] TEXT::zgz::1111 +[2026-05-22 14:13:59] TEXT::zgz::2222 +[2026-05-22 14:14:00] TEXT::zgz::3333 +[2026-05-22 14:14:01] TEXT::zgz::444 +[2026-05-22 14:14:03] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:14:05] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:30:26] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:30:26] SYSTEM::gz 离开聊天室 +[2026-05-22 14:30:31] SYSTEM::gz 加入聊天室 +[2026-05-22 14:30:31] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:32:44] SYSTEM::测试机器人 加入聊天室 +[2026-05-22 14:32:44] TEXT::测试机器人::你好世界 +[2026-05-22 14:32:44] SYSTEM::匿名用户 离开聊天室 +[2026-05-22 14:32:48] SYSTEM::测试机器人2 加入聊天室 +[2026-05-22 14:32:48] TEXT::测试机器人2::这是一条实时测试消息! +[2026-05-22 14:32:48] SYSTEM::测试机器人2 离开聊天室 diff --git a/backups/chat_history_20260522_143305.txt b/backups/chat_history_20260522_143305.txt new file mode 100644 index 0000000..256b941 --- /dev/null +++ b/backups/chat_history_20260522_143305.txt @@ -0,0 +1,2 @@ +[2026-05-22 14:32:59] SYSTEM::nginx测试 加入聊天室 +[2026-05-22 14:32:59] SYSTEM::nginx测试 离开聊天室 diff --git a/backups/chat_history_20260523_030001.txt b/backups/chat_history_20260523_030001.txt new file mode 100644 index 0000000..80dcf48 --- /dev/null +++ b/backups/chat_history_20260523_030001.txt @@ -0,0 +1,131 @@ +[2026-05-22 14:36:47] SYSTEM::gz 离开聊天室 +[2026-05-22 14:36:50] SYSTEM::gz 加入聊天室 +[2026-05-22 14:36:54] TEXT::gz::测试一下 +[2026-05-22 14:36:58] TEXT::gz::发送聊天记录 +[2026-05-22 14:37:19] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:37:21] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:37:25] TEXT::zgz::可以,还不错 +[2026-05-22 14:37:28] TEXT::zgz::哪里 +[2026-05-22 14:37:33] TEXT::zgz::什么啊 +[2026-05-22 14:37:47] TEXT::gz::你是谁 +[2026-05-22 14:40:58] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:41:00] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:41:09] TEXT::zgz::你好 +[2026-05-22 14:41:12] TEXT::zgz::什么 +[2026-05-22 14:41:15] TEXT::zgz::这是哪里的消息. +[2026-05-22 14:41:22] TEXT::zgz::可以啊 +[2026-05-22 14:41:57] TEXT::zgz::刚刚发送了图片,是不是没显示成功 +[2026-05-22 14:42:12] TEXT::zgz::还是说文件太大了 +[2026-05-22 14:43:47] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:43:47] SYSTEM::gz 离开聊天室 +[2026-05-22 14:43:57] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:43:57] SYSTEM::gz 加入聊天室 +[2026-05-22 14:44:00] SYSTEM::gz 离开聊天室 +[2026-05-22 14:44:00] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:44:05] SYSTEM::gz 加入聊天室 +[2026-05-22 14:44:05] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:44:47] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:44:49] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:45:15] TEXT::zgz::还没有成功? +[2026-05-22 14:47:35] SYSTEM::gz 离开聊天室 +[2026-05-22 14:47:37] SYSTEM::gz 加入聊天室 +[2026-05-22 14:47:41] TEXT::gz::我再试一下 +[2026-05-22 14:48:22] TEXT::gz::? +[2026-05-22 14:48:43] SYSTEM::gz 离开聊天室 +[2026-05-22 14:48:46] SYSTEM::gz 加入聊天室 +[2026-05-22 14:53:04] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:53:04] SYSTEM::gz 离开聊天室 +[2026-05-22 14:53:09] SYSTEM::gz 加入聊天室 +[2026-05-22 14:53:10] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:53:10] SYSTEM::gz 离开聊天室 +[2026-05-22 14:53:10] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:53:15] SYSTEM::gz 加入聊天室 +[2026-05-22 14:53:15] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:53:25] SYSTEM::测试用户 加入聊天室 +[2026-05-22 14:53:25] SYSTEM::测试用户 离开聊天室 +[2026-05-22 14:53:41] SYSTEM::gz 离开聊天室 +[2026-05-22 14:53:45] SYSTEM::gz 加入聊天室 +[2026-05-22 14:53:49] TEXT::gz::123 +[2026-05-22 14:54:12] TEXT::gz::这次真可以了. +[2026-05-22 14:56:29] SYSTEM::怀旧用户 加入聊天室 +[2026-05-22 14:56:29] TEXT::怀旧用户::你好,老聊天室的感觉! +[2026-05-22 14:56:29] SYSTEM::怀旧用户 离开聊天室 +[2026-05-22 14:56:57] SYSTEM::gz 离开聊天室 +[2026-05-22 14:57:00] SYSTEM::gz 加入聊天室 +[2026-05-22 14:57:15] TEXT::gz::你好 +[2026-05-22 14:57:22] TEXT::gz::测试一下 +[2026-05-22 14:57:39] SYSTEM::zgz 离开聊天室 +[2026-05-22 14:57:41] SYSTEM::zgz 加入聊天室 +[2026-05-22 14:57:43] TEXT::zgz::还可以啊 +[2026-05-22 14:57:56] TEXT::zgz::继续 +[2026-05-22 14:57:59] TEXT::zgz::测试 +[2026-05-22 14:59:09] SYSTEM::ts 加入聊天室 +[2026-05-22 14:59:32] TEXT::ts::什么? +[2026-05-22 14:59:46] TEXT::ts::东东 +[2026-05-22 15:00:22] SYSTEM::ts 离开聊天室 +[2026-05-22 15:03:16] SYSTEM::TG用户 加入聊天室 +[2026-05-22 15:03:16] TEXT::TG用户::Telegram风格测试 +[2026-05-22 15:03:16] SYSTEM::TG用户 离开聊天室 +[2026-05-22 15:03:32] SYSTEM::zgz 离开聊天室 +[2026-05-22 15:03:35] SYSTEM::zgz 加入聊天室 +[2026-05-22 15:09:15] SYSTEM::设计测试 加入聊天室 +[2026-05-22 15:09:15] TEXT::设计测试::对齐布局测试 +[2026-05-22 15:09:15] SYSTEM::设计测试 离开聊天室 +[2026-05-22 15:09:52] SYSTEM::gz 离开聊天室 +[2026-05-22 15:09:54] SYSTEM::gz 加入聊天室 +[2026-05-22 15:12:39] TEXT::gz::这是 +[2026-05-22 15:12:44] TEXT::gz::什么内容. +[2026-05-22 15:14:58] SYSTEM::群聊测试 加入聊天室 +[2026-05-22 15:14:58] TEXT::群聊测试::所有人都在左侧 +[2026-05-22 15:14:58] SYSTEM::群聊测试 离开聊天室 +[2026-05-22 15:15:41] SYSTEM::gz 离开聊天室 +[2026-05-22 15:15:44] SYSTEM::gz 加入聊天室 +[2026-05-22 15:15:49] TEXT::gz::这能区分出来哪个是我吗 +[2026-05-22 15:15:53] TEXT::gz::哦 +[2026-05-22 15:15:56] TEXT::gz::好像可以 +[2026-05-22 15:16:07] SYSTEM::zgz 离开聊天室 +[2026-05-22 15:16:09] SYSTEM::zgz 加入聊天室 +[2026-05-22 15:16:10] TEXT::zgz::试一下 +[2026-05-22 15:16:12] TEXT::zgz::这个是我 +[2026-05-22 15:16:22] SYSTEM::zgz 离开聊天室 +[2026-05-22 15:16:27] SYSTEM::gz 离开聊天室 +[2026-05-22 15:16:29] SYSTEM::gz 加入聊天室 +[2026-05-22 15:16:43] TEXT::gz::好玩的. +[2026-05-22 15:16:47] TEXT::gz::test +[2026-05-22 15:16:47] TEXT::gz::test +[2026-05-22 15:20:08] SYSTEM::无痕测试 加入聊天室 +[2026-05-22 15:20:08] TEXT::无痕测试::退出重进就看不见了 +[2026-05-22 15:20:08] SYSTEM::无痕测试 离开聊天室 +[2026-05-22 15:20:35] SYSTEM::gz 离开聊天室 +[2026-05-22 15:20:37] SYSTEM::gz 加入聊天室 +[2026-05-22 15:20:51] TEXT::gz::你好,这是一条新的消息. +[2026-05-22 15:20:56] TEXT::gz::开始测试. +[2026-05-22 15:21:08] SYSTEM::gz 加入聊天室 +[2026-05-22 15:21:13] SYSTEM::gz 离开聊天室 +[2026-05-22 15:21:28] SYSTEM::ts 加入聊天室 +[2026-05-22 15:21:36] TEXT::ts::hello +[2026-05-22 15:21:36] TEXT::ts::hello +[2026-05-22 15:21:40] TEXT::ts::可以吗 +[2026-05-22 15:21:43] TEXT::ts::历史 +[2026-05-22 15:22:03] SYSTEM::ts 离开聊天室 +[2026-05-22 15:22:25] TEXT::gz::退出再进消息就没了 +[2026-05-22 16:47:10] SYSTEM::ts 加入聊天室 +[2026-05-22 16:47:11] SYSTEM::ts 离开聊天室 +[2026-05-22 16:47:13] SYSTEM::ts 加入聊天室 +[2026-05-22 16:49:26] SYSTEM::ts 离开聊天室 +[2026-05-22 17:59:32] SYSTEM::gz 离开聊天室 +[2026-05-22 17:59:37] SYSTEM::gz 加入聊天室 +[2026-05-22 18:00:16] SYSTEM::gz 离开聊天室 +[2026-05-22 18:01:20] SYSTEM::gz 加入聊天室 +[2026-05-22 18:02:20] SYSTEM::gz 离开聊天室 +[2026-05-22 18:18:47] SYSTEM::gz 加入聊天室 +[2026-05-22 18:22:10] SYSTEM::gz 离开聊天室 +[2026-05-22 18:38:35] SYSTEM::gz 加入聊天室 +[2026-05-22 18:40:16] SYSTEM::gz 离开聊天室 +[2026-05-22 18:57:14] SYSTEM::gz 加入聊天室 +[2026-05-22 21:05:21] SYSTEM::gz 离开聊天室 +[2026-05-22 21:05:26] SYSTEM::gz 加入聊天室 +[2026-05-22 23:10:35] SYSTEM::gz 离开聊天室 +[2026-05-22 23:15:01] SYSTEM::gz 加入聊天室 +[2026-05-22 23:22:08] SYSTEM::gz 离开聊天室 +[2026-05-22 23:26:34] SYSTEM::gz 加入聊天室 diff --git a/backups/chat_history_20260524_030001.txt b/backups/chat_history_20260524_030001.txt new file mode 100644 index 0000000..ba34c08 --- /dev/null +++ b/backups/chat_history_20260524_030001.txt @@ -0,0 +1,8 @@ +[2026-05-23 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-23 03:01:10] SYSTEM::gz 加入聊天室 +[2026-05-23 09:16:55] SYSTEM::gz 离开聊天室 +[2026-05-23 09:24:58] SYSTEM::gz 加入聊天室 +[2026-05-23 13:21:44] SYSTEM::gz 离开聊天室 +[2026-05-23 13:26:48] SYSTEM::gz 加入聊天室 +[2026-05-23 16:47:08] SYSTEM::gz 离开聊天室 +[2026-05-23 16:47:13] SYSTEM::gz 加入聊天室 diff --git a/backups/chat_history_20260525_030001.txt b/backups/chat_history_20260525_030001.txt new file mode 100644 index 0000000..74c9bf4 --- /dev/null +++ b/backups/chat_history_20260525_030001.txt @@ -0,0 +1,6 @@ +[2026-05-24 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-24 03:01:11] SYSTEM::gz 加入聊天室 +[2026-05-24 21:55:39] SYSTEM::gz 离开聊天室 +[2026-05-24 22:00:06] SYSTEM::gz 加入聊天室 +[2026-05-24 22:02:28] SYSTEM::gz 离开聊天室 +[2026-05-24 22:07:18] SYSTEM::gz 加入聊天室 diff --git a/backups/chat_history_20260526_030001.txt b/backups/chat_history_20260526_030001.txt new file mode 100644 index 0000000..3259e53 --- /dev/null +++ b/backups/chat_history_20260526_030001.txt @@ -0,0 +1,20 @@ +[2026-05-25 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-25 03:01:14] SYSTEM::gz 加入聊天室 +[2026-05-25 09:18:50] SYSTEM::gz 离开聊天室 +[2026-05-25 09:21:12] SYSTEM::gz 加入聊天室 +[2026-05-25 13:31:34] SYSTEM::gz 离开聊天室 +[2026-05-25 13:38:33] SYSTEM::gz 加入聊天室 +[2026-05-25 14:30:06] SYSTEM::gz 加入聊天室 +[2026-05-25 14:31:56] SYSTEM::匿名6524 加入聊天室 +[2026-05-25 14:32:07] SYSTEM::匿名6524 离开聊天室 +[2026-05-25 14:35:01] SYSTEM::gz 离开聊天室 +[2026-05-25 14:35:40] SYSTEM::gz 离开聊天室 +[2026-05-25 14:35:41] SYSTEM::gz 加入聊天室 +[2026-05-25 14:35:53] SYSTEM::zgz 加入聊天室 +[2026-05-25 14:35:55] SYSTEM::zgz 离开聊天室 +[2026-05-25 18:12:17] SYSTEM::gz 离开聊天室 +[2026-05-25 18:12:20] SYSTEM::gz 加入聊天室 +[2026-05-25 18:12:22] SYSTEM::gz 离开聊天室 +[2026-05-25 18:12:24] SYSTEM::gz 加入聊天室 +[2026-05-25 18:13:04] SYSTEM::gz 离开聊天室 +[2026-05-25 18:32:44] SYSTEM::gz 加入聊天室 diff --git a/backups/chat_history_20260527_030001.txt b/backups/chat_history_20260527_030001.txt new file mode 100644 index 0000000..362f637 --- /dev/null +++ b/backups/chat_history_20260527_030001.txt @@ -0,0 +1,26 @@ +[2026-05-26 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-26 03:02:58] SYSTEM::gz 加入聊天室 +[2026-05-26 04:00:38] SYSTEM::gz 离开聊天室 +[2026-05-26 04:05:03] SYSTEM::gz 加入聊天室 +[2026-05-26 09:00:25] SYSTEM::gz 离开聊天室 +[2026-05-26 09:01:05] SYSTEM::gz 加入聊天室 +[2026-05-26 18:04:52] SYSTEM::gz 离开聊天室 +[2026-05-26 18:04:55] SYSTEM::gz 加入聊天室 +[2026-05-26 18:05:35] SYSTEM::gz 离开聊天室 +[2026-05-26 18:20:09] SYSTEM::gz 加入聊天室 +[2026-05-26 18:24:00] SYSTEM::gz 离开聊天室 +[2026-05-26 18:40:12] SYSTEM::gz 加入聊天室 +[2026-05-26 19:47:40] SYSTEM::gz 离开聊天室 +[2026-05-26 20:04:10] SYSTEM::gz 加入聊天室 +[2026-05-26 20:08:33] SYSTEM::gz 离开聊天室 +[2026-05-26 20:25:13] SYSTEM::gz 加入聊天室 +[2026-05-26 20:26:53] SYSTEM::gz 离开聊天室 +[2026-05-26 20:41:49] SYSTEM::gz 加入聊天室 +[2026-05-26 20:52:57] SYSTEM::gz 离开聊天室 +[2026-05-26 21:09:09] SYSTEM::gz 加入聊天室 +[2026-05-26 21:11:10] SYSTEM::gz 离开聊天室 +[2026-05-26 21:26:00] SYSTEM::gz 加入聊天室 +[2026-05-26 21:27:20] SYSTEM::gz 离开聊天室 +[2026-05-26 21:44:19] SYSTEM::gz 加入聊天室 +[2026-05-26 21:45:39] SYSTEM::gz 离开聊天室 +[2026-05-26 22:00:53] SYSTEM::gz 加入聊天室 diff --git a/backups/chat_history_20260528_030001.txt b/backups/chat_history_20260528_030001.txt new file mode 100644 index 0000000..fb5710e --- /dev/null +++ b/backups/chat_history_20260528_030001.txt @@ -0,0 +1,39 @@ +[2026-05-27 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-27 03:01:45] SYSTEM::gz 加入聊天室 +[2026-05-27 04:00:25] SYSTEM::gz 离开聊天室 +[2026-05-27 04:04:50] SYSTEM::gz 加入聊天室 +[2026-05-27 07:59:08] SYSTEM::gz 离开聊天室 +[2026-05-27 08:18:30] SYSTEM::gz 加入聊天室 +[2026-05-27 08:20:11] SYSTEM::gz 离开聊天室 +[2026-05-27 08:36:08] SYSTEM::gz 加入聊天室 +[2026-05-27 08:42:13] SYSTEM::gz 离开聊天室 +[2026-05-27 08:42:14] SYSTEM::gz 加入聊天室 +[2026-05-27 08:42:42] SYSTEM::张卫贤 加入聊天室 +[2026-05-27 08:42:45] TEXT::张卫贤::1 +[2026-05-27 08:42:47] SYSTEM::匿名9712 加入聊天室 +[2026-05-27 08:42:48] TEXT::gz::? +[2026-05-27 08:42:49] SYSTEM::匿名9712 离开聊天室 +[2026-05-27 08:42:54] SYSTEM::gz 离开聊天室 +[2026-05-27 08:42:56] SYSTEM::gz 加入聊天室 +[2026-05-27 08:42:59] TEXT::gz::哈 +[2026-05-27 08:45:15] SYSTEM::匿名6572 加入聊天室 +[2026-05-27 08:45:17] SYSTEM::匿名6572 离开聊天室 +[2026-05-27 08:46:16] SYSTEM::张卫贤 加入聊天室 +[2026-05-27 08:46:31] SYSTEM::张卫贤 离开聊天室 +[2026-05-27 08:56:47] SYSTEM::张卫贤 离开聊天室 +[2026-05-27 10:15:46] SYSTEM::匿名3899 加入聊天室 +[2026-05-27 10:15:47] SYSTEM::匿名3899 离开聊天室 +[2026-05-27 16:18:34] SYSTEM::gz 离开聊天室 +[2026-05-27 16:18:44] SYSTEM::gz 加入聊天室 +[2026-05-27 18:01:59] SYSTEM::gz 离开聊天室 +[2026-05-27 18:02:02] SYSTEM::gz 加入聊天室 +[2026-05-27 18:02:41] SYSTEM::gz 离开聊天室 +[2026-05-27 18:17:23] SYSTEM::gz 加入聊天室 +[2026-05-27 18:18:43] SYSTEM::gz 离开聊天室 +[2026-05-27 18:34:51] SYSTEM::gz 加入聊天室 +[2026-05-27 20:32:01] SYSTEM::gz 离开聊天室 +[2026-05-27 20:42:44] SYSTEM::gz 加入聊天室 +[2026-05-27 23:06:33] SYSTEM::gz 离开聊天室 +[2026-05-27 23:06:39] SYSTEM::gz 加入聊天室 +[2026-05-27 23:44:03] SYSTEM::gz 加入聊天室 +[2026-05-27 23:44:19] SYSTEM::gz 离开聊天室 diff --git a/backups/chat_history_20260529_030001.txt b/backups/chat_history_20260529_030001.txt new file mode 100644 index 0000000..4fd4f75 --- /dev/null +++ b/backups/chat_history_20260529_030001.txt @@ -0,0 +1,21 @@ +[2026-05-28 03:00:06] SYSTEM::gz 离开聊天室 +[2026-05-28 03:01:42] SYSTEM::gz 加入聊天室 +[2026-05-28 11:15:18] SYSTEM::gz 离开聊天室 +[2026-05-28 11:18:26] SYSTEM::gz 加入聊天室 +[2026-05-28 11:32:23] SYSTEM::gz 离开聊天室 +[2026-05-28 11:34:51] SYSTEM::gz 加入聊天室 +[2026-05-28 11:37:14] SYSTEM::gz 离开聊天室 +[2026-05-28 11:37:18] SYSTEM::gz 加入聊天室 +[2026-05-28 12:42:05] SYSTEM::gz 离开聊天室 +[2026-05-28 12:42:09] SYSTEM::gz 加入聊天室 +[2026-05-28 13:08:21] SYSTEM::gz 离开聊天室 +[2026-05-28 13:09:56] SYSTEM::gz 加入聊天室 +[2026-05-28 13:14:47] SYSTEM::gz 离开聊天室 +[2026-05-28 13:14:51] SYSTEM::gz 加入聊天室 +[2026-05-28 14:04:28] SYSTEM::ts 加入聊天室 +[2026-05-28 14:05:34] SYSTEM::ts 离开聊天室 +[2026-05-28 14:14:02] SYSTEM::ts 加入聊天室 +[2026-05-28 14:17:20] SYSTEM::ts 离开聊天室 +[2026-05-28 14:20:23] SYSTEM::gz 离开聊天室 +[2026-05-28 14:48:35] SYSTEM::ts 加入聊天室 +[2026-05-28 14:50:54] SYSTEM::ts 离开聊天室 diff --git a/chat.py b/chat.py new file mode 100644 index 0000000..f1b430d --- /dev/null +++ b/chat.py @@ -0,0 +1,300 @@ +""" +ChatHub - 实时聊天室 +FastAPI + WebSocket +""" +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, Form, Request +from fastapi.responses import HTMLResponse, JSONResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from typing import List, Dict, Tuple +import os +import uuid +import json +import asyncio +import shutil +import time +import collections +from datetime import datetime + +app = FastAPI() + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +TEMPLATE_DIR = os.path.join(BASE_DIR, "templates") +UPLOAD_DIR = os.path.join(BASE_DIR, "uploads") +BACKUP_DIR = os.path.join(BASE_DIR, "backups") +os.makedirs(UPLOAD_DIR, exist_ok=True) +os.makedirs(BACKUP_DIR, exist_ok=True) + +CHAT_LOG_FILE = os.path.join(BASE_DIR, "chat_history.txt") +MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB per file + +ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp", "heic"} +ALLOWED_VIDEO_EXTENSIONS = {"mp4", "mov", "wmv", "avi", "m4v", "mpg", "mpeg", "flv", "mkv", "3gp", "webm"} + +templates = Jinja2Templates(directory=TEMPLATE_DIR) +app.mount("/uploads", StaticFiles(directory=UPLOAD_DIR), name="uploads") + +# ── Global State ──────────────────────────────────────────── +connections: Dict[WebSocket, str] = {} # ws -> username +glock = asyncio.Lock() + +# ── Rate Limiting ───────────────────────────────────────── +# Upload rate limit: {ip: [timestamp, ...]} +upload_history: Dict[str, List[float]] = {} +UPLOAD_COOLDOWN = 3.0 # seconds between uploads + +# Message rate limit: per-connection message timestamps +msg_history: Dict[WebSocket, List[float]] = {} +MSG_BURST = 8 # max messages +MSG_WINDOW = 3.0 # per this many seconds + + +def get_ext(fn: str) -> str: + return fn.rsplit(".", 1)[-1].lower() + + +def is_image(ext: str) -> bool: + return ext in ALLOWED_IMAGE_EXTENSIONS + + +def is_video(ext: str) -> bool: + return ext in ALLOWED_VIDEO_EXTENSIONS + + +def log_to_file(msg: str): + ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + with open(CHAT_LOG_FILE, "a", encoding="utf-8") as f: + f.write(f"[{ts}] {msg}\n") + except Exception: + pass + + +def load_recent_history(n: int = 200) -> List[str]: + if not os.path.exists(CHAT_LOG_FILE): + return [] + try: + with open(CHAT_LOG_FILE, "r", encoding="utf-8") as f: + lines = f.readlines() + return [l.strip() for l in lines[-n:]] + except Exception: + return [] + + +# ── HTTP Endpoints ────────────────────────────────────────── + +@app.get("/", response_class=HTMLResponse) +async def index(request: Request): + history = load_recent_history(200) + return templates.TemplateResponse("chat.html", {"request": request, "history": history}) + + +@app.get("/members") +async def get_members(): + """Return current online member list.""" + async with glock: + names = list(connections.values()) + return JSONResponse({"members": names}) + + +# ── Rate Limiter Helpers ──────────────────────────────── + +async def _check_upload_rate(request: Request) -> Tuple[bool, str]: + """Check upload rate limit per client IP. + Returns (allowed, reason). + """ + ip = request.client.host if request.client else "unknown" + now = time.time() + + async with glock: + times = upload_history.get(ip, []) + # Clean old entries + times = [t for t in times if now - t < UPLOAD_COOLDOWN] + if times: + remaining = int(UPLOAD_COOLDOWN - (now - times[-1])) + return False, f"上传太频繁,请 {remaining} 秒后再试" + times.append(now) + upload_history[ip] = times + return True, "" + + +async def _check_msg_rate(ws: WebSocket) -> bool: + """Check message send rate per WebSocket connection.""" + now = time.time() + async with glock: + times = msg_history.get(ws, []) + times = [t for t in times if now - t < MSG_WINDOW] + if len(times) >= MSG_BURST: + return False + times.append(now) + msg_history[ws] = times + return True + + +# ── HTTP Endpoints ────────────────────────────────────────── + +@app.get("/", response_class=HTMLResponse) +async def index(request: Request): + history = load_recent_history(200) + return templates.TemplateResponse("chat.html", {"request": request, "history": history}) + + +@app.get("/members") +async def get_members(): + """Return current online member list.""" + async with glock: + names = list(connections.values()) + return JSONResponse({"members": names}) + + +@app.post("/upload") +async def upload_file(file: UploadFile = File(...), request: Request = None, sender: str = Form("匿名")): + # Rate limit check + allowed, reason = await _check_upload_rate(request) + if not allowed: + return JSONResponse({"error": reason}, status_code=429) + + # File type check + ext = get_ext(file.filename) + if not (is_image(ext) or is_video(ext)): + return JSONResponse({"error": "不支持的文件类型,仅接受图片和视频"}, status_code=400) + + # Read and check size + data = await file.read() + if len(data) > MAX_FILE_SIZE: + size_mb = len(data) / (1024 * 1024) + return JSONResponse({ + "error": f"文件过大({size_mb:.1f}MB),最大允许 10MB" + }, status_code=413) + + # Save and broadcast + fn = f"{uuid.uuid4().hex}.{ext}" + path = os.path.join(UPLOAD_DIR, fn) + with open(path, "wb") as f: + f.write(data) + url = f"/uploads/{fn}" + # Format: TYPE::sender::url — frontend parses two colons + msg = f"IMG::{sender}::{url}" if is_image(ext) else f"VIDEO::{sender}::{url}" + asyncio.create_task(_broadcast(msg)) + return {"url": url} + + +# ── Broadcast ─────────────────────────────────────────────── + +async def _broadcast(message: str): + """Send message to all connected clients. + Uses snapshot-then-send pattern: grab targets under lock, then send concurrently without lock. + """ + async with glock: + targets = list(connections.items()) # [(ws, name), ...] + + tasks = [] + for ws, _ in targets: + tasks.append(_safe_send(ws, message)) + if tasks: + await asyncio.gather(*tasks, return_exceptions=True) + + +async def _safe_send(ws: WebSocket, message: str): + """Send text to one WebSocket, remove on failure.""" + try: + await ws.send_text(message) + except Exception: + async with glock: + connections.pop(ws, None) + + +async def _broadcast_members(): + """Broadcast JSON member list and count to all clients.""" + async with glock: + names = list(connections.values()) + count = len(names) + payload = json.dumps({"type": "members", "data": names}) + await _broadcast(f"MEMBERS::{payload}") + await _broadcast(f"COUNT::{count}") + + +# ── WebSocket Endpoint ────────────────────────────────────── + +@app.websocket("/wschat") +async def ws_endpoint(ws: WebSocket): + await ws.accept() + + # ── 1) Receive username as first text frame ── + try: + raw = await asyncio.wait_for(ws.receive_text(), timeout=30) + except Exception: + await ws.close(1008) + return + + username = (raw.strip() or f"匿名{id(ws) % 10000}")[:20] + + async with glock: + connections[ws] = username + + join_msg = f"SYSTEM::{username} 加入聊天室" + log_to_file(join_msg) + asyncio.create_task(_broadcast(join_msg)) + asyncio.create_task(_broadcast_members()) + + # ── 2) Message loop ── + try: + while True: + text = await ws.receive_text() + if not text or not text.strip(): + continue + # Rate limit check + if not await _check_msg_rate(ws): + asyncio.create_task(_safe_send(ws, "SYSTEM::⚠️ 发送太频繁,请稍后再试")) + continue + msg = f"TEXT::{username}::{text.strip()}" + log_to_file(msg) + asyncio.create_task(_broadcast(msg)) + except (WebSocketDisconnect, Exception): + pass + finally: + async with glock: + name = connections.pop(ws, None) or "匿名用户" + leave_msg = f"SYSTEM::{name} 离开聊天室" + log_to_file(leave_msg) + asyncio.create_task(_broadcast(leave_msg)) + asyncio.create_task(_broadcast_members()) + + +# ── Nightly Cleanup ───────────────────────────────────────── + +@app.post("/api/nightly-cleanup") +async def nightly_cleanup(): + """Backup chat history to backups/ dir, then clear the log. + Also purge uploaded files older than 24h. + Called by cron / systemd timer. + """ + now = datetime.now() + backup_name = f"chat_history_{now.strftime('%Y%m%d_%H%M%S')}.txt" + backup_path = os.path.join(BACKUP_DIR, backup_name) + + if os.path.exists(CHAT_LOG_FILE) and os.path.getsize(CHAT_LOG_FILE) > 0: + shutil.copy2(CHAT_LOG_FILE, backup_path) + # Clear log + open(CHAT_LOG_FILE, "w", encoding="utf-8").close() + + # Clean old uploads (> 24h) + now_ts = time.time() + cleaned = 0 + for fname in os.listdir(UPLOAD_DIR): + fpath = os.path.join(UPLOAD_DIR, fname) + if os.path.isfile(fpath) and now_ts - os.path.getmtime(fpath) > 86400: + try: + os.remove(fpath) + cleaned += 1 + except Exception: + pass + + return {"status": "ok", "backup": backup_name, "cleaned_uploads": cleaned} + + +# ── Main ──────────────────────────────────────────────────── + +if __name__ == "__main__": + import uvicorn + uvicorn.run("chat:app", host="0.0.0.0", port=8202, reload=False, log_level="info") diff --git a/chat.py_20260506 b/chat.py_20260506 new file mode 100644 index 0000000..7c709da --- /dev/null +++ b/chat.py_20260506 @@ -0,0 +1,76 @@ +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from fastapi.requests import Request +import uvicorn + +app = FastAPI() + +# 挂载模板目录 +templates = Jinja2Templates(directory="templates") + +# 活跃连接列表 +active_connections = [] + +# WebSocket 与昵称映射表 +usernames = {} + +# 首页路由 +@app.get("/") +async def get(request: Request): + return templates.TemplateResponse("chat.html", {"request": request}) + +# WebSocket 路由 +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + active_connections.append(websocket) + await broadcast_online_count() # 一连上就广播在线人数 + + try: + # 接收第一个消息作为昵称 + username = await websocket.receive_text() + usernames[websocket] = username + + await broadcast(f"[系统]{username} 加入了聊天室") + await broadcast_online_count() + + while True: + data = await websocket.receive_text() + await broadcast(f"{username}:{data}") + + except WebSocketDisconnect: + active_connections.remove(websocket) + left_username = usernames.pop(websocket, "匿名用户") + + await broadcast(f"[系统]{left_username} 离开了聊天室") + await broadcast_online_count() + +# 广播消息给所有连接 +async def broadcast(message: str): + to_remove = [] + for connection in active_connections: + try: + await connection.send_text(message) + except: + to_remove.append(connection) + for conn in to_remove: + active_connections.remove(conn) + usernames.pop(conn, None) + +# 广播在线人数 +async def broadcast_online_count(): + message = f"[人数]{len(active_connections)}" + to_remove = [] + for conn in active_connections: + try: + await conn.send_text(message) + except: + to_remove.append(conn) + for conn in to_remove: + active_connections.remove(conn) + usernames.pop(conn, None) + +if __name__ == "__main__": + uvicorn.run("chat:app", host="0.0.0.0", port=8202) \ No newline at end of file diff --git a/chat.py_new b/chat.py_new new file mode 100644 index 0000000..3d6db09 --- /dev/null +++ b/chat.py_new @@ -0,0 +1,160 @@ +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, Request +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +import os +import shutil +import uuid +import uvicorn + +app = FastAPI() + +# ========================= +# 基础配置 +# ========================= + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +TEMPLATE_DIR = os.path.join(BASE_DIR, "templates") +UPLOAD_DIR = os.path.join(BASE_DIR, "uploads") + +MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB + +ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif"} +ALLOWED_VIDEO_EXTENSIONS = {"mp4", "webm"} + +# ========================= +# 目录初始化 +# ========================= + +os.makedirs(UPLOAD_DIR, exist_ok=True) + +templates = Jinja2Templates(directory=TEMPLATE_DIR) +app.mount("/uploads", StaticFiles(directory=UPLOAD_DIR), name="uploads") + +# ========================= +# WebSocket 状态 +# ========================= + +active_connections: list[WebSocket] = [] +usernames: dict[WebSocket, str] = {} + +# ========================= +# 工具函数 +# ========================= + +def get_ext(filename: str) -> str: + return filename.rsplit(".", 1)[-1].lower() + +def is_image(ext: str) -> bool: + return ext in ALLOWED_IMAGE_EXTENSIONS + +def is_video(ext: str) -> bool: + return ext in ALLOWED_VIDEO_EXTENSIONS + +# ========================= +# 页面路由 +# ========================= + +@app.get("/", response_class=HTMLResponse) +async def index(request: Request): + return templates.TemplateResponse("chat.html", {"request": request}) + +# ========================= +# 文件上传接口 +# ========================= + +@app.post("/upload") +async def upload_file(file: UploadFile = File(...)): + ext = get_ext(file.filename) + + if not (is_image(ext) or is_video(ext)): + return {"error": "不支持的文件类型"} + + contents = await file.read() + + if len(contents) > MAX_FILE_SIZE: + return {"error": "文件过大(最大 10MB)"} + + filename = f"{uuid.uuid4().hex}.{ext}" + save_path = os.path.join(UPLOAD_DIR, filename) + + with open(save_path, "wb") as f: + f.write(contents) + + file_url = f"/uploads/{filename}" + + if is_image(ext): + msg = f"IMG::{file_url}" + elif is_video(ext): + msg = f"VIDEO::{file_url}" + else: + msg = f"FILE::{file_url}" + + await broadcast(msg) + + return {"url": file_url} + +# ========================= +# WebSocket 聊天 +# ========================= + +@app.websocket("/ws") +async def websocket_endpoint(ws: WebSocket): + await ws.accept() + active_connections.append(ws) + await broadcast_online_count() + + try: + # 首条消息作为用户名 + username = await ws.receive_text() + usernames[ws] = username + + await broadcast(f"SYSTEM::{username} 加入聊天室") + await broadcast_online_count() + + while True: + text = await ws.receive_text() + await broadcast(f"TEXT::{username}::{text}") + + except WebSocketDisconnect: + pass + + finally: + if ws in active_connections: + active_connections.remove(ws) + name = usernames.pop(ws, "匿名用户") + await broadcast(f"SYSTEM::{name} 离开聊天室") + await broadcast_online_count() + +# ========================= +# 广播工具 +# ========================= + +async def broadcast(message: str): + dead = [] + for ws in active_connections: + try: + await ws.send_text(message) + except Exception: + dead.append(ws) + + for ws in dead: + active_connections.remove(ws) + usernames.pop(ws, None) + +async def broadcast_online_count(): + msg = f"COUNT::{len(active_connections)}" + await broadcast(msg) + +# ========================= +# 启动 +# ========================= + +if __name__ == "__main__": + uvicorn.run( + "chat:app", + host="0.0.0.0", + port=8202, + reload=True + ) \ No newline at end of file diff --git a/chat_history.txt b/chat_history.txt new file mode 100644 index 0000000..e69de29 diff --git a/nightly_cleanup.log b/nightly_cleanup.log new file mode 100644 index 0000000..7b3f5aa --- /dev/null +++ b/nightly_cleanup.log @@ -0,0 +1,21 @@ +[2026-05-23 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260523_030001.txt","cleaned_uploads":0} +[2026-05-23 03:00:01] Cleanup completed. +[2026-05-24 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260524_030001.txt","cleaned_uploads":7} +[2026-05-24 03:00:01] Cleanup completed. +[2026-05-25 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260525_030001.txt","cleaned_uploads":0} +[2026-05-25 03:00:01] Cleanup completed. +[2026-05-26 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260526_030001.txt","cleaned_uploads":0} +[2026-05-26 03:00:01] Cleanup completed. +[2026-05-27 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260527_030001.txt","cleaned_uploads":0} +[2026-05-27 03:00:01] Cleanup completed. +[2026-05-28 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260528_030001.txt","cleaned_uploads":0} +[2026-05-28 03:00:01] Cleanup completed. +[2026-05-29 03:00:01] Starting nightly cleanup... +{"status":"ok","backup":"chat_history_20260529_030001.txt","cleaned_uploads":0} +[2026-05-29 03:00:01] Cleanup completed. diff --git a/nightly_cleanup.sh b/nightly_cleanup.sh new file mode 100755 index 0000000..5dab654 --- /dev/null +++ b/nightly_cleanup.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Nightly cleanup for ChatHub +# Backups chat history, clears log, removes old uploads +# Called by cron at 03:00 daily + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +LOG_FILE="${SCRIPT_DIR}/nightly_cleanup.log" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting nightly cleanup..." >> "$LOG_FILE" + +# Call the API endpoint +curl -s -X POST http://localhost:8202/api/nightly-cleanup >> "$LOG_FILE" 2>&1 + +echo "" >> "$LOG_FILE" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Cleanup completed." >> "$LOG_FILE" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..528606b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +jinja2 \ No newline at end of file diff --git a/templates/chat.html b/templates/chat.html new file mode 100644 index 0000000..6c2f705 --- /dev/null +++ b/templates/chat.html @@ -0,0 +1,821 @@ + + + + + + ChatHub + + + +
+ + + + +
+ +
+
+
💬
+
+
无痕聊天室
+
🟡 连接中...
+
+
+
+ 🌙 +
+
+ + +
+
+
🫧
+
聊天记录为空
+
发出一条消息开始聊天吧
+
+
+ + +
+
+
+ + +
+ +
+ +
+ + +
+
+
+ + + + diff --git a/templates/chat.html_ b/templates/chat.html_ new file mode 100644 index 0000000..d77f6e7 --- /dev/null +++ b/templates/chat.html_ @@ -0,0 +1,199 @@ + + + + 纵有千古、横有八荒 + + + + + +
+
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/templates/chat.html_20260506 b/templates/chat.html_20260506 new file mode 100644 index 0000000..f5af34e --- /dev/null +++ b/templates/chat.html_20260506 @@ -0,0 +1,354 @@ + + + 无痕聊天室 + + + + + +
+
🌙 夜间
+
在线人数:0
+
🟢 已连接
+
+
+ + +
+
+ + + + diff --git a/templates/chat.html_bak b/templates/chat.html_bak new file mode 100644 index 0000000..1d8f588 --- /dev/null +++ b/templates/chat.html_bak @@ -0,0 +1,140 @@ + + + + 纵有千古、横有八荒 + + + + +
+ +
+ + +
+
+ + + + \ No newline at end of file diff --git a/templates/chat.html_bak2 b/templates/chat.html_bak2 new file mode 100644 index 0000000..0651872 --- /dev/null +++ b/templates/chat.html_bak2 @@ -0,0 +1,246 @@ + + + 无痕聊天室 + + + + + + + + +
+
🌙 夜间
+
在线人数:0
+
🟢 已连接
+ +
+ +
+ + + + +
+
+ + + + \ No newline at end of file diff --git a/templates/chat.html_bak3 b/templates/chat.html_bak3 new file mode 100644 index 0000000..0651872 --- /dev/null +++ b/templates/chat.html_bak3 @@ -0,0 +1,246 @@ + + + 无痕聊天室 + + + + + + + + +
+
🌙 夜间
+
在线人数:0
+
🟢 已连接
+ +
+ +
+ + + + +
+
+ + + + \ No newline at end of file diff --git a/templates/chat.html_new b/templates/chat.html_new new file mode 100644 index 0000000..e6ccd2f --- /dev/null +++ b/templates/chat.html_new @@ -0,0 +1,234 @@ + + + 无痕聊天室 + + + + + + + + +
+
🌙 夜间
+
在线人数:0
+
🟢 已连接
+ +
+ +
+ + + + +
+
+ + + + \ No newline at end of file diff --git a/uwsgi.ini b/uwsgi.ini new file mode 100644 index 0000000..b80aed2 --- /dev/null +++ b/uwsgi.ini @@ -0,0 +1,30 @@ +[uwsgi] +uid = uwsgi +gid = uwsgi + +# 启动服务监听的地址和端口 +http-socket = 0.0.0.0:8202 + +# 指定虚拟环境路径 +virtualenv = /opt/service/python_prj/pictoHub.env + +# 指定 Flask 应用文件的路径 +wsgi-file = /opt/service/python_prj/chatHub/chat.py + +# 设置 Flask 的应用实例 +callable = app + +# 设置静态文件目录映射 +static-map = /static=/opt/service/python_prj/chatHub/static/ + +# 日志文件 +logto = /var/log/uwsgi/chat-project.log + +# 设置进程数 +processes = 4 + +# 启动时的 Python 环境路径 +home = /opt/service/python_prj/pictoHub.env + +# 确保应用正常启动 +touch-reload = /opt/service/python_prj/chatHub/chat.py