diff --git a/bot/main.py b/bot/main.py index 6bf49fe..17258f4 100644 --- a/bot/main.py +++ b/bot/main.py @@ -253,19 +253,116 @@ async def show_my_accounts(callback: CallbackQuery): reply_markup=main_menu_keyboard(is_admin(callback.from_user.id)) ) else: - text = "Your SIP accounts:\n\n" + keyboard = InlineKeyboardMarkup(inline_keyboard=[]) + for acc in accounts: - text += f"Number: {acc['extension_number']}\n" - if acc['sip_secret']: - text += f"Password: {acc['sip_secret']}\n" - text += f"Created: {acc['created_at']}\n\n" + keyboard.inline_keyboard.append([ + InlineKeyboardButton( + text=f"📞 {acc['extension_number']}", + callback_data=f"my_account_{acc['extension_number']}" + ) + ]) + + keyboard.inline_keyboard.append([ + InlineKeyboardButton(text="Back", callback_data="back_main") + ]) await callback.message.edit_text( - text, - reply_markup=main_menu_keyboard(is_admin(callback.from_user.id)) + "Your SIP accounts. Select one to view details:", + reply_markup=keyboard ) await callback.answer() +@dp.callback_query(F.data.startswith("my_account_")) +async def show_my_account_details(callback: CallbackQuery): + number = callback.data.replace("my_account_", "") + account = await db.get_sip_account_by_number(number) + + if not account: + await callback.answer("Account not found.") + return + + text = ( + f"Account: {account['extension_number']}\n" + f"Password: {account['sip_secret'] or 'Not set'}\n" + f"Created: {account['created_at']}\n\n" + "What would you like to do?" + ) + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Show connection parameters", callback_data=f"show_params_{number}")], + [InlineKeyboardButton(text="Delete this account", callback_data=f"delete_my_account_{number}")], + [InlineKeyboardButton(text="Back to my accounts", callback_data="my_accounts")] + ]) + + await callback.message.edit_text(text, reply_markup=keyboard) + await callback.answer() + +@dp.callback_query(F.data.startswith("show_params_")) +async def show_connection_parameters(callback: CallbackQuery): + number = callback.data.replace("show_params_", "") + account = await db.get_sip_account_by_number(number) + + if not account: + await callback.answer("Account not found.") + return + + connection_info = ( + f"Connection parameters for {number}:\n\n" + f"Domain: {DEFAULT_SIP_DOMAIN}\n" + f"Server: {DEFAULT_SIP_SERVER}\n" + f"Port: {DEFAULT_SIP_PORT}\n" + f"Transport: {DEFAULT_SIP_TRANSPORT}\n" + f"Username: {number}\n" + f"Password: {account['sip_secret']}\n" + ) + + if DEFAULT_SIP_OUTBOUND_PROXY: + connection_info += f"Outbound Proxy: {DEFAULT_SIP_OUTBOUND_PROXY}\n" + if DEFAULT_SIP_STUN: + connection_info += f"STUN: {DEFAULT_SIP_STUN}\n" + + connection_info += "\nRecommended clients: Zoiper, MicroSIP, Linphone" + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Back", callback_data=f"my_account_{number}")] + ]) + + await callback.message.edit_text(connection_info, reply_markup=keyboard) + await callback.answer() + +@dp.callback_query(F.data.startswith("delete_my_account_")) +async def confirm_delete_my_account(callback: CallbackQuery): + number = callback.data.replace("delete_my_account_", "") + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Yes, delete", callback_data=f"confirm_delete_my_{number}")], + [InlineKeyboardButton(text="Cancel", callback_data=f"my_account_{number}")] + ]) + + await callback.message.edit_text( + f"Are you sure you want to delete extension {number}?\n" + "This action cannot be undone.", + reply_markup=keyboard + ) + await callback.answer() + +@dp.callback_query(F.data.startswith("confirm_delete_my_")) +async def delete_my_account(callback: CallbackQuery): + number = callback.data.replace("confirm_delete_my_", "") + + # Delete from MikoPBX + await mikopbx.delete_extension(number) + + # Delete from local DB + await db.delete_sip_account(number) + + await callback.message.edit_text( + f"Extension {number} has been deleted.", + reply_markup=main_menu_keyboard(is_admin(callback.from_user.id)) + ) + await callback.answer() + # ============== ADMIN HANDLERS ============== @dp.callback_query(F.data == "admin_menu") @@ -406,12 +503,20 @@ async def admin_view_account(callback: CallbackQuery): await callback.answer("Account not found.") return + # Get owner info + owner = await db.get_user(account.get('telegram_id', 0)) if account.get('telegram_id') else None + owner_info = "" + if owner: + owner_info = f"Owner: @{owner.get('telegram_username', 'N/A')} (ID: {owner['telegram_id']})\n" + else: + owner_info = f"Owner Telegram ID: {account.get('telegram_id', 'N/A')}\n" + text = ( f"Account details:\n\n" f"Number: {account['extension_number']}\n" f"Password: {account['sip_secret'] or 'Not set'}\n" f"Username: {account.get('username', 'N/A')}\n" - f"Owner Telegram ID: {account.get('telegram_id', 'N/A')}\n" + f"{owner_info}" f"Created: {account['created_at']}\n\n" f"Actions:" ) diff --git a/bot/services/mikopbx_service.py b/bot/services/mikopbx_service.py index 8073375..4c277db 100644 --- a/bot/services/mikopbx_service.py +++ b/bot/services/mikopbx_service.py @@ -129,22 +129,55 @@ class MikoPBXService: "error": error } + async def get_employee_by_number(self, number: str) -> Optional[Dict]: + """Get employee record by extension number (returns full record with real 'id')""" + # Get list and search by number + response = await self._make_request("GET", "/pbxcore/api/v3/employees") + if not response.get("result"): + return None + + employees = response.get("data", []) + for emp in employees: + if isinstance(emp, dict) and emp.get("number") == number: + return emp + return None + async def get_extension(self, number: str) -> Optional[Dict]: """Get employee by number""" - response = await self._make_request("GET", f"/pbxcore/api/v3/employees/{number}") - return response.get("data") if response.get("result") else None + return await self.get_employee_by_number(number) async def set_extension_secret(self, number: str, new_secret: str) -> Dict[str, Any]: - current = await self.get_extension(number) + current = await self.get_employee_by_number(number) if not current: return {"success": False, "error": "Extension not found"} + + real_id = current.get("id") current["sip_secret"] = new_secret - response = await self._make_request("PUT", f"/pbxcore/api/v3/employees/{number}", json_data=current) + + response = await self._make_request( + "PUT", + f"/pbxcore/api/v3/employees/{real_id}", + json_data=current + ) return {"success": response.get("result", False), "messages": response.get("messages", [])} async def delete_extension(self, number: str) -> Dict[str, Any]: - response = await self._make_request("DELETE", f"/pbxcore/api/v3/employees/{number}") - return {"success": response.get("result", False), "messages": response.get("messages", [])} + """Delete employee by extension number (finds real ID first)""" + employee = await self.get_employee_by_number(number) + if not employee: + return {"success": False, "error": "Employee not found"} + + real_id = employee.get("id") + logger.info(f">>> Deleting employee with real ID: {real_id}") + + response = await self._make_request( + "DELETE", + f"/pbxcore/api/v3/employees/{real_id}" + ) + return { + "success": response.get("result", False), + "messages": response.get("messages", []) + } async def close(self): if self.session: