Update files

This commit is contained in:
glpshchn 2025-11-11 03:40:29 +03:00
parent d83399e5f9
commit 8c81bac776
3 changed files with 230 additions and 9 deletions

View File

@ -249,10 +249,7 @@ const handleCommand = async (message) => {
const command = args[0].toLowerCase();
if (command === '/start') {
await sendMessage(
chatId,
'<b>NakamaHost Moderation</b>\nКоманды:\n• /load — состояние сервера\n• /admins — список админов\n• /addadmin @username — добавить админа (только владельцы)\n• /removeadmin @username — убрать админа (только владельцы)'
);
await sendMessage(chatId, '✅ Бот активен');
return;
}
@ -293,9 +290,7 @@ const handleCommand = async (message) => {
return;
}
if (command.startsWith('/')) {
await sendMessage(chatId, 'Неизвестная команда. Используйте /start, /load, /admins.');
}
// Игнорируем неизвестные команды
};
const processUpdate = async (update) => {

View File

@ -10,7 +10,12 @@ import {
banPostAuthor,
fetchReports,
updateReportStatus,
publishToChannel
publishToChannel,
fetchAdmins,
initiateAddAdmin,
confirmAddAdmin,
initiateRemoveAdmin,
confirmRemoveAdmin
} from './utils/api';
import { io } from 'socket.io-client';
import {
@ -23,13 +28,17 @@ import {
RefreshCw,
Trash2,
Edit,
Ban
Ban,
UserPlus,
UserMinus,
Crown
} from 'lucide-react';
const TABS = [
{ id: 'users', title: 'Пользователи', icon: Users },
{ id: 'posts', title: 'Посты', icon: ImageIcon },
{ id: 'reports', title: 'Репорты', icon: ShieldCheck },
{ id: 'admins', title: 'Админы', icon: Crown },
{ id: 'chat', title: 'Чат', icon: MessageSquare },
{ id: 'publish', title: 'Публикация', icon: SendHorizontal }
];
@ -91,6 +100,12 @@ export default function App() {
});
const [publishing, setPublishing] = useState(false);
// Admins
const [adminsData, setAdminsData] = useState({ admins: [] });
const [adminsLoading, setAdminsLoading] = useState(false);
const [adminModal, setAdminModal] = useState(null); // { action: 'add'|'remove', user/admin, adminNumber }
const [confirmCode, setConfirmCode] = useState('');
// Chat
const [chatState, setChatState] = useState(initialChatState);
const [chatInput, setChatInput] = useState('');
@ -150,6 +165,8 @@ export default function App() {
loadPosts();
} else if (tab === 'reports') {
loadReports();
} else if (tab === 'admins') {
loadAdmins();
} else if (tab === 'chat' && user) {
initChat();
}
@ -200,6 +217,65 @@ export default function App() {
}
};
const loadAdmins = async () => {
setAdminsLoading(true);
try {
const data = await fetchAdmins();
setAdminsData(data);
} catch (err) {
console.error(err);
} finally {
setAdminsLoading(false);
}
};
const handleInitiateAddAdmin = async (targetUser, adminNumber) => {
try {
const result = await initiateAddAdmin(targetUser.id, adminNumber);
alert(`Код отправлен ${result.username}. Попросите пользователя ввести код.`);
setAdminModal({ action: 'add', user: targetUser, adminNumber });
} catch (err) {
alert(err.response?.data?.error || 'Ошибка отправки кода');
}
};
const handleConfirmAddAdmin = async () => {
if (!adminModal || !confirmCode) return;
try {
await confirmAddAdmin(adminModal.user.id, confirmCode);
alert(`Админ ${adminModal.user.username} добавлен!`);
setAdminModal(null);
setConfirmCode('');
loadAdmins();
loadUsers();
} catch (err) {
alert(err.response?.data?.error || 'Ошибка подтверждения');
}
};
const handleInitiateRemoveAdmin = async (admin) => {
try {
const result = await initiateRemoveAdmin(admin.id);
alert(`Код отправлен ${result.username}. Попросите админа ввести код.`);
setAdminModal({ action: 'remove', admin });
} catch (err) {
alert(err.response?.data?.error || 'Ошибка отправки кода');
}
};
const handleConfirmRemoveAdmin = async () => {
if (!adminModal || !confirmCode) return;
try {
await confirmRemoveAdmin(adminModal.admin.id, confirmCode);
alert(`Админ ${adminModal.admin.username} удалён!`);
setAdminModal(null);
setConfirmCode('');
loadAdmins();
} catch (err) {
alert(err.response?.data?.error || 'Ошибка подтверждения');
}
};
const initChat = () => {
if (!user || chatSocketRef.current) return;
const socket = io('/mod-chat', {
@ -370,6 +446,19 @@ export default function App() {
</div>
</div>
<div className="list-item-actions">
{user.username === 'glpshchn00' && !u.isAdmin && (
<button
className="btn"
onClick={() => {
const num = prompt('Введите номер админа (1-10):', '1');
if (num && !isNaN(num) && num >= 1 && num <= 10) {
handleInitiateAddAdmin(u, parseInt(num));
}
}}
>
<UserPlus size={16} /> Назначить
</button>
)}
{u.banned ? (
<button className="btn" onClick={() => handleBanUser(u.id, false)}>
Разблокировать
@ -599,6 +688,126 @@ export default function App() {
</div>
);
const renderAdmins = () => (
<div className="card">
<div className="section-header">
<h2>Админы модерации</h2>
<button className="icon-btn" onClick={loadAdmins} disabled={adminsLoading}>
<RefreshCw size={18} />
</button>
</div>
{adminsLoading ? (
<div className="loader">
<Loader2 className="spin" size={32} />
</div>
) : (
<div className="list">
{adminsData.admins.map((admin) => (
<div key={admin.id} className="list-item">
<div className="list-item-main">
<div className="list-item-title">
<Crown size={16} color="gold" /> @{admin.username} Номер {admin.adminNumber}
</div>
<div className="list-item-subtitle">
{admin.firstName} {admin.lastName || ''}
</div>
<div className="list-item-meta">
<span>Добавил: {admin.addedBy}</span>
<span>Дата: {formatDate(admin.createdAt)}</span>
</div>
</div>
{user.username === 'glpshchn00' && (
<div className="list-item-actions">
<button
className="btn danger"
onClick={() => handleInitiateRemoveAdmin(admin)}
>
<UserMinus size={16} /> Снять
</button>
</div>
)}
</div>
))}
{adminsData.admins.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-secondary)' }}>
Нет админов
</div>
)}
</div>
)}
{/* Модальное окно подтверждения */}
{adminModal && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.8)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000
}}>
<div style={{
background: 'var(--background)',
padding: '20px',
borderRadius: '12px',
maxWidth: '400px',
width: '90%'
}}>
<h3 style={{ marginTop: 0 }}>
{adminModal.action === 'add' ? 'Подтверждение добавления админа' : 'Подтверждение удаления админа'}
</h3>
<p>
{adminModal.action === 'add'
? `Код отправлен пользователю @${adminModal.user.username}. Введите код для подтверждения:`
: `Код отправлен админу @${adminModal.admin.username}. Введите код для подтверждения:`
}
</p>
<input
type="text"
placeholder="6-значный код"
value={confirmCode}
onChange={(e) => setConfirmCode(e.target.value)}
style={{
width: '100%',
padding: '12px',
fontSize: '16px',
border: '1px solid var(--border)',
borderRadius: '8px',
background: 'var(--background-secondary)',
color: 'var(--text-primary)',
marginBottom: '16px'
}}
maxLength={6}
/>
<div style={{ display: 'flex', gap: '8px' }}>
<button
className="btn"
onClick={adminModal.action === 'add' ? handleConfirmAddAdmin : handleConfirmRemoveAdmin}
disabled={confirmCode.length !== 6}
>
Подтвердить
</button>
<button
className="btn"
onClick={() => {
setAdminModal(null);
setConfirmCode('');
}}
>
Отмена
</button>
</div>
</div>
</div>
)}
</div>
);
const renderContent = () => {
switch (tab) {
case 'users':
@ -607,6 +816,8 @@ export default function App() {
return renderPosts();
case 'reports':
return renderReports();
case 'admins':
return renderAdmins();
case 'chat':
return renderChat();
case 'publish':

View File

@ -89,5 +89,20 @@ export const publishToChannel = (formData) =>
}
})
export const fetchAdmins = () =>
api.get('/mod-app/admins').then((res) => res.data)
export const initiateAddAdmin = (userId, adminNumber) =>
api.post('/mod-app/admins/initiate-add', { userId, adminNumber }).then((res) => res.data)
export const confirmAddAdmin = (userId, code) =>
api.post('/mod-app/admins/confirm-add', { userId, code }).then((res) => res.data)
export const initiateRemoveAdmin = (adminId) =>
api.post('/mod-app/admins/initiate-remove', { adminId }).then((res) => res.data)
export const confirmRemoveAdmin = (adminId, code) =>
api.post('/mod-app/admins/confirm-remove', { adminId, code }).then((res) => res.data)
export default api