This commit is contained in:
@ -1,63 +0,0 @@
|
||||
import asyncio
|
||||
import mcstatus
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
from mcstatus.responses import JavaStatusResponse
|
||||
|
||||
from konabot.common.permsys import require_permission
|
||||
|
||||
|
||||
cmd = on_command(
|
||||
"宾几人",
|
||||
aliases=set(("宾人数", "mcbingo")),
|
||||
rule=require_permission("minecraft.bingo.check"),
|
||||
)
|
||||
|
||||
|
||||
def parse_status(motd: str) -> str:
|
||||
if "[PRE-GAME]" in motd:
|
||||
return "[✨ 空闲]"
|
||||
if "[IN-GAME]" in motd:
|
||||
return "[🕜 游戏中]"
|
||||
if "[POST-GAME]" in motd:
|
||||
return "[🕜 游戏中]"
|
||||
return "[✨ 开放]"
|
||||
|
||||
|
||||
def dump_server_status(name: str, status: JavaStatusResponse | BaseException) -> str:
|
||||
if isinstance(status, JavaStatusResponse):
|
||||
motd = status.motd.to_plain()
|
||||
# Bingo Status: [PRE-GAME], [IN-GAME], [POST-GAME]
|
||||
st = parse_status(motd)
|
||||
players_sample = status.players.sample or []
|
||||
players_sample_suffix = ""
|
||||
if len(players_sample) > 0:
|
||||
player_list = [s.name for s in players_sample]
|
||||
players_sample_suffix = " (" + ", ".join(player_list) + ")"
|
||||
return f"{name}: {st} {status.players.online} 人在线{players_sample_suffix}"
|
||||
else:
|
||||
return f"{name}: 好像没开"
|
||||
|
||||
|
||||
@cmd.handle()
|
||||
async def _(evt: Event):
|
||||
servers = (
|
||||
(mcstatus.JavaServer("play.simpfun.cn", 11495), "小帕 Bingo"),
|
||||
(mcstatus.JavaServer("bingo.mujica.tech"), "坏枪 Bingo"),
|
||||
(mcstatus.JavaServer("mc.mujica.tech", 11456), "齿轮盛宴"),
|
||||
)
|
||||
|
||||
responses = await asyncio.gather(
|
||||
*map(lambda s: s[0].async_status(), servers),
|
||||
return_exceptions=True,
|
||||
)
|
||||
messages = "\n".join(
|
||||
(
|
||||
dump_server_status(n, r)
|
||||
for n, r in zip(map(lambda s: s[1], servers), responses)
|
||||
)
|
||||
)
|
||||
|
||||
await UniMessage.text(messages).finish(evt, at_sender=False)
|
||||
131
konabot/plugins/minecraft_servers/__init__.py
Normal file
131
konabot/plugins/minecraft_servers/__init__.py
Normal file
@ -0,0 +1,131 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
from typing import Literal
|
||||
import mcstatus
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Event
|
||||
from nonebot_plugin_alconna import Alconna, Args, UniMessage, on_alconna
|
||||
from mcstatus.responses import JavaStatusResponse
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
from konabot.common.permsys import DepPermManager, require_permission
|
||||
from konabot.plugins.minecraft_servers.simpfun_server import SimpfunServer
|
||||
|
||||
|
||||
cmd = on_command(
|
||||
"宾几人",
|
||||
aliases=set(("宾人数", "mcbingo")),
|
||||
rule=require_permission("minecraft.bingo.check"),
|
||||
)
|
||||
|
||||
|
||||
def parse_status(motd: str) -> str:
|
||||
if "[PRE-GAME]" in motd:
|
||||
return "[✨ 空闲]"
|
||||
if "[IN-GAME]" in motd:
|
||||
return "[🕜 游戏中]"
|
||||
if "[POST-GAME]" in motd:
|
||||
return "[🕜 游戏中]"
|
||||
return "[✨ 开放]"
|
||||
|
||||
|
||||
def dump_server_status(name: str, status: JavaStatusResponse | BaseException) -> str:
|
||||
if isinstance(status, JavaStatusResponse):
|
||||
motd = status.motd.to_plain()
|
||||
# Bingo Status: [PRE-GAME], [IN-GAME], [POST-GAME]
|
||||
st = parse_status(motd)
|
||||
players_sample = status.players.sample or []
|
||||
players_sample_suffix = ""
|
||||
if len(players_sample) > 0:
|
||||
player_list = [s.name for s in players_sample]
|
||||
players_sample_suffix = " (" + ", ".join(player_list) + ")"
|
||||
return f"{name}: {st} {status.players.online} 人在线{players_sample_suffix}"
|
||||
else:
|
||||
return f"{name}: 好像没开"
|
||||
|
||||
|
||||
@cmd.handle()
|
||||
async def _(evt: Event, pm: DepPermManager):
|
||||
servers = (
|
||||
(mcstatus.JavaServer("play.simpfun.cn", 11495), "小帕 Bingo"),
|
||||
(mcstatus.JavaServer("bingo.mujica.tech"), "坏枪 Bingo"),
|
||||
(mcstatus.JavaServer("mc.mujica.tech", 11456), "齿轮盛宴"),
|
||||
)
|
||||
|
||||
responses = await asyncio.gather(
|
||||
*map(lambda s: s[0].async_status(), servers),
|
||||
return_exceptions=True,
|
||||
)
|
||||
messages = "\n".join(
|
||||
(
|
||||
dump_server_status(n, r)
|
||||
for n, r in zip(map(lambda s: s[1], servers), responses)
|
||||
)
|
||||
)
|
||||
|
||||
if await pm.check_has_permission(evt, "minecraft.bingo.manipulate"):
|
||||
messages += "\n\n---\n\n你可以使用 bingoman start 开启小帕的 bingo 服,用 bingoman stop 关闭小帕的 bingo 服"
|
||||
|
||||
await UniMessage.text(messages).finish(evt, at_sender=False)
|
||||
|
||||
|
||||
cmd_bingo_manipulate = on_alconna(
|
||||
Alconna("bingoman", Args["action", str]),
|
||||
aliases=("宾服务器", "bingo服"),
|
||||
rule=require_permission("minecraft.bingo.manipulate"),
|
||||
)
|
||||
|
||||
actions: dict[str, Literal["start", "stop", "restart", "kill"]] = {
|
||||
"up": "start",
|
||||
"down": "stop",
|
||||
"start": "start",
|
||||
"stop": "stop",
|
||||
"开机": "start",
|
||||
"关机": "stop",
|
||||
"restart": "restart",
|
||||
"kill": "kill",
|
||||
"重启": "restart",
|
||||
}
|
||||
|
||||
|
||||
@cmd_bingo_manipulate.handle()
|
||||
async def _(action: str, event: Event):
|
||||
server = SimpfunServer.new() # 使用默认配置管理服务器
|
||||
a = actions.get(action.lower().strip())
|
||||
if a is None:
|
||||
await UniMessage.text(f"操作 {action} 不存在").send(event, at_sender=True)
|
||||
return
|
||||
resp = await server.power(a)
|
||||
if resp.code == 200:
|
||||
await UniMessage.text("好了").send(event, at_sender=True)
|
||||
else:
|
||||
await UniMessage.text(f"不好:{resp}").send(event, at_sender=True)
|
||||
|
||||
|
||||
@scheduler.scheduled_job("cron", hour="4,23")
|
||||
async def _():
|
||||
server = SimpfunServer.new()
|
||||
today = datetime.datetime.now()
|
||||
|
||||
# 获取服务器当前状态,重试多次以保证不会误判服务器未开启
|
||||
server_up = False
|
||||
server_players = 0
|
||||
for _ in range(3):
|
||||
mcs = mcstatus.JavaServer("play.simpfun.cn", 11495)
|
||||
try:
|
||||
resp = await mcs.async_status()
|
||||
server_up = True
|
||||
server_players = resp.players.online
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if today.weekday() == 5 and today.hour < 12:
|
||||
# 每周六开机一天,保证可以让服务器不被自动销毁
|
||||
if not server_up:
|
||||
await server.power("start")
|
||||
else:
|
||||
# 每用一个自然日都会计费,所以要赶在这一天结束之前关服
|
||||
# 平时如果没人,也自动关上
|
||||
if server_up and server_players == 0:
|
||||
await server.power("stop")
|
||||
90
konabot/plugins/minecraft_servers/simpfun_server.py
Normal file
90
konabot/plugins/minecraft_servers/simpfun_server.py
Normal file
@ -0,0 +1,90 @@
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
from typing import Literal
|
||||
|
||||
import aiohttp
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SimpfunServerConfig(BaseModel):
|
||||
plugin_simpfun_api_key: str = ""
|
||||
plugin_simpfun_base_url: str = "https://api.simpfun.cn"
|
||||
plugin_simpfun_instance_id: int = 0
|
||||
|
||||
|
||||
def get_config():
|
||||
from nonebot import get_plugin_config
|
||||
|
||||
return get_plugin_config(SimpfunServerConfig)
|
||||
|
||||
|
||||
class PowerManageResult(BaseModel):
|
||||
code: int
|
||||
status: bool
|
||||
msg: str
|
||||
|
||||
|
||||
class SimpfunServerDetailUtilization(BaseModel):
|
||||
memory_bytes: int
|
||||
cpu_absolute: float
|
||||
disk_bytes: int
|
||||
network_rx_bytes: int
|
||||
network_tx_bytes: int
|
||||
uptime: float
|
||||
disk_last_check_time: datetime.datetime
|
||||
|
||||
|
||||
class SimpfunServerDetailData(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
is_pro: bool
|
||||
|
||||
status: str
|
||||
"运行中的话,是 running"
|
||||
|
||||
is_suspended: bool
|
||||
utilization: SimpfunServerDetailUtilization
|
||||
|
||||
|
||||
class SimpfunServerDetailResp(BaseModel):
|
||||
code: int
|
||||
data: SimpfunServerDetailData
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimpfunServer:
|
||||
instance_id: int
|
||||
api_key: str
|
||||
base_url: str
|
||||
|
||||
async def power(
|
||||
self, action: Literal["start", "stop", "restart", "kill"]
|
||||
) -> PowerManageResult:
|
||||
url = f"{self.base_url}/api/ins/{self.instance_id}/power"
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
headers={"Authorization": self.api_key}
|
||||
) as session:
|
||||
async with session.get(url, params={"action": action}) as resp:
|
||||
resp.raise_for_status()
|
||||
return PowerManageResult.model_validate_json(await resp.read())
|
||||
|
||||
async def detail(self) -> SimpfunServerDetailResp:
|
||||
url = f"{self.base_url}/api/ins/{self.instance_id}/power"
|
||||
|
||||
async with aiohttp.ClientSession(
|
||||
headers={"Authorization": self.api_key}
|
||||
) as session:
|
||||
async with session.get(url) as resp:
|
||||
resp.raise_for_status()
|
||||
return SimpfunServerDetailResp.model_validate_json(await resp.read())
|
||||
|
||||
@staticmethod
|
||||
def new(config: SimpfunServerConfig | None = None):
|
||||
if config is None:
|
||||
config = get_config()
|
||||
return SimpfunServer(
|
||||
instance_id=config.plugin_simpfun_instance_id,
|
||||
api_key=config.plugin_simpfun_api_key,
|
||||
base_url=config.plugin_simpfun_base_url,
|
||||
)
|
||||
Reference in New Issue
Block a user