Files
autman-plugins/py/nazha.py
T
2026-05-15 21:26:06 +08:00

460 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#[title: nazha]
#[language: python]
#[service: 10086] 售后联系方式
#[disable: false] 禁用开关,true表示禁用,false表示可用
#[admin: true] 是否为管理员指令
#[rule: ^哪吒查询$]
#[rule: ^哪吒管理$]
#[cron: 0 7 * * *]
#[priority: 300] 优先级,数字越大表示优先级越高
#[platform: qq,wx,tg,tb] 适用的平台
#[open_source: true]是否开源
#[icon: 图标url]图标链接地址,请使用48像素的正方形图标,支持http和https
#[version: 1.0.0]版本号
#[public: false] 是否发布?值为true或false,不设置则上传aut云时会自动设置为true,false时上传后不显示在市场中,但是搜索能搜索到,方便开发者测试
#[price: 0.01] 上架价格
# [description: 【简介】:哪吒监控查询 支持多账号管理,可查询服务器状态、服务状态等]
# [icon: https://cdn.jsdelivr.net/gh/naiba/nezha@master/resource/static/img/nezha.png]
# =======================
import importlib
import inspect
import json
import logging
import math
from math import fabs
import os
import random
import subprocess
import sys
import time
from datetime import datetime, timedelta
from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
from urllib.parse import urlencode
from wsgiref import headers
import requests
import aiohttp
import asyncio
import middleware
plugin_pre = 'vhook_nezha_'
class TOOL:
def __init__(self):
self.plat = ["qq", "wx", "wb", "qb", "bt", "tg"]
self.platform_arr = self.platformArr()
self.sender = middleware.Sender(middleware.getSenderID())
self.plugin_title = self.sender.getPluginName()
self.plugin_ver = self.sender.getPluginVersion()
self.plugin_name = self.sender.getPluginName()
self.msg = self.sender.getMessage().strip('"')
self.imType = "fake" if self.sender.getImtype() == "cron" else self.sender.getImtype()
self.chatId = self.sender.getChatID()
self.userId = self.sender.getUserID()
self.isAdmin = self.sender.isAdmin()
self.remark = ""
self.timeStr = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 安装必须依赖
self.dependencies = ['colorlog', "requests", "aiohttp"]
self.install_dependencies()
self.logger = logging.getLogger()
self.color_formatter = sys.modules['colorlog'].ColoredFormatter(
'%(log_color)s%(levelname)s: %(message)s',
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
}
)
def install_dependencies(self):
for dependency in self.dependencies:
try:
importlib.import_module(dependency)
except ImportError as e:
self.pushErr2Master(f"【错误】:缺少【{dependency}】模块, 正在自动安装,请稍后....")
if self.check_pip_version("pip3"):
cmd = "pip3"
else:
cmd = "pip"
result = subprocess.run([cmd, 'install', dependency], capture_output=True, text=True)
if result.returncode == 0:
self.pushErr2Master(f"依赖【{dependency}】安装完成,请重新发送指令,运行插件")
def check_pip_version(self, pip_command):
try:
result = subprocess.run([pip_command, '--version'], capture_output=True, text=True)
if result.returncode == 0:
return True
except FileNotFoundError:
pass
return False
def get_log(self, level=logging.INFO):
self.logger.setLevel(level)
console_handler = logging.StreamHandler()
console_handler.setLevel(level)
console_handler.setFormatter(self.color_formatter)
for handler in self.logger.handlers:
self.logger.removeHandler(handler)
self.logger.addHandler(console_handler)
def log_info(self, msg):
self.get_log(logging.INFO)
return self.logger.info(f"{msg}")
def log_err(self, msg):
self.get_log(logging.ERROR)
return self.logger.error(f"{msg}")
def log_warn(self, msg):
self.get_log(logging.WARN)
return self.logger.warning(f"{msg}")
def platformArr(self):
plat_arr = []
for item in self.plat:
t = {"platform": f"{plugin_pre}{item}", "imType": item}
plat_arr.append(t)
return plat_arr
def pushMsg(self, userId, chatId, imType, title, content):
now = datetime.now()
formatted_datetime = now.strftime('%Y-%m-%d %H:%M:%S')
self.remark = ""
msg = f"*********{tool.plugin_title}*********"
msg += f'\n【任务时间】:{formatted_datetime}'
msg += f"\n{content}"
middleware.push(imType, chatId, userId, title, msg)
return
def pushGroup(self, imtype, groupCode, content):
self.pushMsg(0, groupCode, imtype, "", content)
def pushErr2Master(self, content):
middleware.notifyMasters(
f"【插件】:{self.plugin_title}\n {content}",
['qq', 'tg', 'wx', 'wb', ])
def replyMsg(self, content):
return self.sender.reply(content)
class ACCOUNT:
def __init__(self):
self.method = "account"
self.plugin_title = "哪吒监控"
self.attr_arr = [
{
"title": "备注",
"key": "name",
"timeOut": 60000
}, {
"title": "面板地址",
"key": "dashboard_url",
"timeOut": 60000
}, {
"title": "用户名",
"key": "username",
"timeOut": 60000
}, {
"title": "密码",
"key": "password",
"timeOut": 60000
}
]
def setVal(self, item, title, key, timeOut):
tool.log_info(item)
tool.replyMsg(f"请输入{title}")
value = tool.sender.listen(timeOut)
tool.log_info(f"{key}==={value}")
if value == "q" or value == "error":
tool.replyMsg("输入有误/超时,已退出!")
return False
item[key] = value
return True
def addCount(self):
tool.replyMsg("哪吒监控配置说明:\n1. 备注:用于区分不同账号\n2. 面板地址:哪吒面板的完整URL\n3. 用户名:面板登录用户名\n4. 密码:面板登录密码")
item = {}
go = True
for attr in self.attr_arr:
if not self.setVal(item, attr['title'], attr['key'], attr['timeOut']):
go = False
break
if not go:
tool.log_info("数据配置有误,已退出!")
return tool.replyMsg("数据配置有误,已退出!")
item_arr = []
item_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
if item_str:
item_arr = json.loads(item_str)
item_arr.append(item)
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
tool.replyMsg("数据已配置完成,开始自动任务")
item['push_user_id'] = tool.userId
item['push_im_type'] = tool.imType
item['index'] = 1
item['total'] = 1
READ(item).run()
def editCount(self, item_arr, no):
item = item_arr[no]
content = f"请在【2分钟】内输入 序号,编辑对应的属性(q:退出)"
content += f"\n--------------------"
content += f"\n输入数字:0 删除此账号!"
content += f"\n--------------------"
for index, attr in enumerate(self.attr_arr):
if attr['key'] not in item:
item[attr['key']] = ""
content += f"\n{index + 1}.【{attr['title']}】:{item[attr['key']]}"
tool.replyMsg(content)
value = tool.sender.listen(120000)
if value == "q" or value == "error":
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
return tool.replyMsg("已退出!")
if not value.isdigit():
return self.editCount(item_arr, no)
if 0 < int(value) <= len(self.attr_arr):
attr = self.attr_arr[int(value) - 1]
if self.setVal(item, attr['title'], attr['key'], 12000):
item_arr[no] = item
self.editCount(item_arr, no)
elif value == "0":
item_arr.pop(no)
if len(item_arr) == 0:
middleware.bucketDel(f"{plugin_pre}{tool.imType}", tool.userId)
else:
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
return tool.replyMsg(f"已删除第{no + 1}个账号信息!请重新发送:哪吒管理 ")
else:
return self.editCount(item_arr, no)
def accoount_manager(self):
item_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
if not item_str or item_str == "":
tool.replyMsg(f"未配置账号,请发送:哪吒查询")
exit(1)
item_arr = json.loads(item_str)
content = f"请选择要账号查看详情:(0增加, q退出)\n"
for index, item in enumerate(item_arr):
if not hasattr(item, ""):
item['disable'] = True
disable = item['disable']
status = '已启用' if disable else '已禁用'
content = "".join([content, f"\n{index + 1}{item['name']}"])
tool.replyMsg(content)
value = tool.sender.listen(60000)
if value == "q" or value == "error":
tool.replyMsg("输入有误,已退出!")
elif value == "0":
self.addCount()
elif value.isdigit() and 0 < int(value) <= len(item_arr):
self.editCount(item_arr, int(value) - 1)
else:
tool.replyMsg(f"输入有误,请重新发送:哪吒管理,并输入正确的序号!")
def cron_account_arr(self):
account_arr = []
for plat in tool.platformArr():
p = plat["platform"]
user_id_arr = middleware.bucketAllKeys(p)
if not user_id_arr:
continue
for index, user_id in enumerate(user_id_arr):
user_data_str = middleware.bucketGet(p, user_id)
if not user_data_str or user_id == '':
continue
user_data_arr_temp = json.loads(user_data_str)
for n, account_data in enumerate(user_data_arr_temp):
account_data["push_user_id"] = user_id
account_data["push_im_type"] = plat["imType"]
account_data['index'] = n + 1
account_data['total'] = len(user_data_arr)
account_arr.append(account_data)
return account_arr
def account_task(self, item, no):
read = READ(item)
read.run()
class READ:
def __init__(self, account):
self.name = account['name']
self.dashboard_url = account['dashboard_url']
self.username = account['username']
self.password = account['password']
self.index = account['index']
self.total = account['total']
self.push_user_id = account['push_user_id']
self.push_im_type = account['push_im_type']
self.msg = ''
self.dateStr = datetime.now().strftime('%Y-%m-%d')
self.timeStr = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.servers = [] # 初始化servers属性
self.chat_id = None # 初始化chat_id属性
def pushMsg(self, msg):
content = f"【账号备注】:{self.name}"
content += f'{msg}'
tool.pushMsg(self.push_user_id, None, self.push_im_type, "", content)
def format_bytes(self, size_in_bytes):
if size_in_bytes == 0:
return "0B"
units = ['B', 'KB', 'MB', 'GB', 'TB']
power = int(math.floor(math.log(size_in_bytes, 1024)))
power = min(power, len(units) - 1)
size = size_in_bytes / (1024 ** power)
formatted_size = f"{size:.2f}{units[power]}"
return formatted_size
async def get_server_info(self):
try:
async with aiohttp.ClientSession() as session:
# 登录获取token
login_url = f"{self.dashboard_url.rstrip('/')}/api/v1/login"
login_data = {
"username": self.username,
"password": self.password
}
async with session.post(login_url, json=login_data) as resp:
login_result = await resp.json()
if not login_result.get('success'):
return False, "登录失败,请检查用户名和密码"
token = login_result['data']['token']
# 获取服务器信息
headers = {"Authorization": f"Bearer {token}"}
server_url = f"{self.dashboard_url.rstrip('/')}/api/v1/server"
async with session.get(server_url, headers=headers) as resp:
server_result = await resp.json()
if not server_result.get('success'):
return False, "获取服务器信息失败"
self.servers = server_result['data'] # 填充servers属性
# 计算总体统计信息
online_count = sum(1 for server in self.servers if server.get('state', {}).get('last_active'))
total_mem = sum(server.get('host', {}).get('mem_total', 0) for server in self.servers)
used_mem = sum(server.get('state', {}).get('mem_used', 0) for server in self.servers)
total_disk = sum(server.get('host', {}).get('disk_total', 0) for server in self.servers)
used_disk = sum(server.get('state', {}).get('disk_used', 0) for server in self.servers)
total_swap = sum(server.get('host', {}).get('swap_total', 0) for server in self.servers)
used_swap = sum(server.get('state', {}).get('swap_used', 0) for server in self.servers)
total_net_in = sum(server.get('state', {}).get('net_in_transfer', 0) for server in self.servers)
total_net_out = sum(server.get('state', {}).get('net_out_transfer', 0) for server in self.servers)
# 返回总体统计信息
self.msg += f"\n【服务器数量】:{len(self.servers)}"
self.msg += f"\n【在线服务器】:{online_count}"
self.msg += f"\n【内存使用】:{self.format_bytes(used_mem)}/{self.format_bytes(total_mem)}"
self.msg += f"\n【磁盘使用】:{self.format_bytes(used_disk)}/{self.format_bytes(total_disk)}"
self.msg += f"\n【交换使用】:{self.format_bytes(used_swap)}/{self.format_bytes(total_swap)}"
self.msg += f"\n【总流量】:↓{self.format_bytes(total_net_in)}{self.format_bytes(total_net_out)}"
return True, "获取成功"
except Exception as e:
return False, f"获取信息失败:{str(e)}"
def run(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
success, message = loop.run_until_complete(self.get_server_info())
loop.close()
if not success:
self.msg += f"\n【错误】:{message}"
self.pushMsg(self.msg)
return
self.display_menu()
def display_menu(self):
# 只返回总体数据和选单
self.msg = f"\n【服务器数量】:{len(self.servers)}"
self.msg += f"\n【在线服务器】:{sum(1 for server in self.servers if server.get('state', {}).get('last_active'))}"
self.msg += f"\n【内存使用】:{self.format_bytes(sum(server.get('state', {}).get('mem_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('mem_total', 0) for server in self.servers))}"
self.msg += f"\n【磁盘使用】:{self.format_bytes(sum(server.get('state', {}).get('disk_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('disk_total', 0) for server in self.servers))}"
self.msg += f"\n【交换使用】:{self.format_bytes(sum(server.get('state', {}).get('swap_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('swap_total', 0) for server in self.servers))}"
self.msg += f"\n【总流量】:↓{self.format_bytes(sum(server.get('state', {}).get('net_in_transfer', 0) for server in self.servers))}{self.format_bytes(sum(server.get('state', {}).get('net_out_transfer', 0) for server in self.servers))}"
self.msg += "\n\n【服务器编号选单】:"
for index, server in enumerate(self.servers):
server_name = server.get('name', '未知')
self.msg += f"\n{index + 1}. {server_name}"
self.msg += "\n0. 查询所有服务器信息"
self.pushMsg(self.msg)
# 等待用户输入
user_input = tool.sender.listen(30000) # 30秒超时
if user_input.lower() == 'q':
tool.replyMsg("对话已结束。")
elif user_input.isdigit():
index = int(user_input) - 1
if index == -1:
# 查询所有服务器信息
all_details = ""
for i in range(len(self.servers)):
all_details += self.query_server_by_index(i) + "\n"
tool.replyMsg(all_details)
#tool.replyMsg("查询结束。")
elif 0 <= index < len(self.servers):
details = self.query_server_by_index(index)
tool.replyMsg(details)
self.display_menu() # 返回到二级菜单
else:
tool.replyMsg("无效的编号,请重新输入。")
self.display_menu()
else:
tool.replyMsg("超时或无效输入,已自动退出。")
def query_server_by_index(self, index):
if index < 0 or index >= len(self.servers):
return "无效的服务器编号。"
server = self.servers[index]
server_name = server.get('name', '未知')
status = "在线" if server.get('state', {}).get('last_active') else ""
details = f"\n{server_name}{status}"
details += f"\nCPU使用率:{server.get('state', {}).get('cpu', 0):.2f}%"
details += f"\n内存使用:{self.format_bytes(server.get('state', {}).get('mem_used', 0))}/{self.format_bytes(server.get('host', {}).get('mem_total', 0))}"
details += f"\n磁盘使用:{self.format_bytes(server.get('state', {}).get('disk_used', 0))}/{self.format_bytes(server.get('host', {}).get('disk_total', 0))}"
details += f"\n流量统计:↓{self.format_bytes(server.get('state', {}).get('net_in_transfer', 0))}{self.format_bytes(server.get('state', {}).get('net_out_transfer', 0))}"
details += f"\n当前下载速率:↓{self.format_bytes(server.get('state', {}).get('net_in_speed', 0))}/s"
details += f"\n当前上传速率:↑{self.format_bytes(server.get('state', {}).get('net_out_speed', 0))}/s"
return details
if __name__ == "__main__":
tool = TOOL()
account = ACCOUNT()
if tool.msg == '哪吒管理':
account.accoount_manager()
exit(1)
if tool.imType == 'fake':
user_data_arr = account.cron_account_arr()
else:
user_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
if not user_str or user_str == "":
account.addCount()
exit(1)
user_data_arr = json.loads(user_str)
for i, user_data in enumerate(user_data_arr):
user_data["push_user_id"] = tool.userId
user_data["push_im_type"] = tool.imType
user_data['index'] = i + 1
user_data['total'] = len(user_data_arr)
if tool.sender.param(1):
user_data_arr = [user_data_arr[int(tool.sender.param(1))-1]]
for account_data in user_data_arr:
account.account_task(account_data, account_data['index'])
exit(1)