Add HeXi bot codebase: custom plugins, web frontends, tests
- hexi core: message handling, rate limiting, cooldown, plugin manager - Custom plugins: BF stats, daily check-in, quotes, persona cards, etc. - Community plugins vendored under hexi/plugins with local fixes - Web admin frontends (learning-chat, persona-admin), unified hexi/web - Tests for rate_limit/cooldown/memes/persona; poetry.lock Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import asyncio
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import TypeAlias
|
||||
|
||||
import psutil
|
||||
from httpx import AsyncClient, ReadTimeout
|
||||
from psutil._common import snetio
|
||||
|
||||
from ..config import TestSiteCfg, config
|
||||
from ..util import match_list_regexp
|
||||
from . import (
|
||||
BaseTimeBasedCounterCollector,
|
||||
NormalTimeBasedCounterCollector,
|
||||
PeriodicTimeBasedCounterCollector,
|
||||
collector,
|
||||
normal_collector,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkIO:
|
||||
name: str
|
||||
sent: float
|
||||
recv: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkConnectionOK:
|
||||
name: str
|
||||
status: int
|
||||
reason: str
|
||||
delay: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkConnectionError:
|
||||
name: str
|
||||
error: str
|
||||
|
||||
|
||||
NetworkConnectionType: TypeAlias = NetworkConnectionOK | NetworkConnectionError
|
||||
|
||||
|
||||
class BaseNetworkIOCollector(
|
||||
BaseTimeBasedCounterCollector[dict[str, snetio], list[NetworkIO]],
|
||||
):
|
||||
async def _calc(
|
||||
self,
|
||||
past: dict[str, snetio],
|
||||
now: dict[str, snetio],
|
||||
time_passed: float,
|
||||
) -> list[NetworkIO]:
|
||||
def calc_one(name: str, past_it: snetio, now_it: snetio) -> NetworkIO | None:
|
||||
if match_list_regexp(config.ps_ignore_nets, name):
|
||||
# logger.info(f"网卡IO统计 {name} 匹配 {regex.re.pattern},忽略")
|
||||
return None
|
||||
|
||||
sent = (now_it.bytes_sent - past_it.bytes_sent) / time_passed
|
||||
recv = (now_it.bytes_recv - past_it.bytes_recv) / time_passed
|
||||
|
||||
if sent == 0 and recv == 0 and config.ps_ignore_0b_net:
|
||||
# logger.info(f"网卡IO统计 忽略无IO网卡 {name}")
|
||||
return None
|
||||
|
||||
return NetworkIO(name=name, sent=sent, recv=recv)
|
||||
|
||||
res = [calc_one(name, past[name], now[name]) for name in past if name in now]
|
||||
res = [x for x in res if x]
|
||||
if config.ps_sort_nets:
|
||||
res.sort(key=lambda x: x.sent + x.recv, reverse=True)
|
||||
return res
|
||||
|
||||
async def _get_obj(self) -> dict[str, snetio]:
|
||||
return psutil.net_io_counters(pernic=True)
|
||||
|
||||
|
||||
@collector("network_io")
|
||||
class NormalDiskIOCollector(
|
||||
BaseNetworkIOCollector,
|
||||
NormalTimeBasedCounterCollector[dict[str, snetio], list[NetworkIO]],
|
||||
): ...
|
||||
|
||||
|
||||
@collector("network_io_periodic")
|
||||
class PeriodicDiskIOCollector(
|
||||
BaseNetworkIOCollector,
|
||||
PeriodicTimeBasedCounterCollector[dict[str, snetio], list[NetworkIO]],
|
||||
): ...
|
||||
|
||||
|
||||
@normal_collector()
|
||||
async def network_connection() -> list[NetworkConnectionType]:
|
||||
def format_conn_error(error: Exception) -> str:
|
||||
if isinstance(error, ReadTimeout):
|
||||
return "超时"
|
||||
return error.__class__.__name__
|
||||
|
||||
async def test_one(site: TestSiteCfg) -> NetworkConnectionType:
|
||||
try:
|
||||
async with AsyncClient(
|
||||
timeout=config.ps_test_timeout,
|
||||
proxy=config.proxy if site.use_proxy else None,
|
||||
follow_redirects=True,
|
||||
) as client:
|
||||
start = time.time()
|
||||
resp = await client.get(str(site.url))
|
||||
delay = (time.time() - start) * 1000
|
||||
|
||||
except Exception as e:
|
||||
return NetworkConnectionError(name=site.name, error=format_conn_error(e))
|
||||
|
||||
return NetworkConnectionOK(
|
||||
name=site.name,
|
||||
status=resp.status_code,
|
||||
reason=resp.reason_phrase,
|
||||
delay=delay,
|
||||
)
|
||||
|
||||
res = await asyncio.gather(*map(test_one, config.ps_test_sites))
|
||||
if config.ps_sort_sites:
|
||||
res.sort(key=lambda x: x.delay if isinstance(x, NetworkConnectionOK) else -1)
|
||||
|
||||
return res
|
||||
Reference in New Issue
Block a user