Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93e22a8364 | ||
|
|
d514601651 |
No files matched your search
@@ -31,6 +31,9 @@ class StatChannel(commands.Cog):
|
|||||||
async with session.get('https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=1282270') as r:
|
async with session.get('https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=1282270') as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
count = data['response']['player_count']
|
count = data['response']['player_count']
|
||||||
|
if not isinstance(count, int) or count <= 0:
|
||||||
|
print(f'stat channel update skipped (channel {channel_id}): steam returned {count!r}')
|
||||||
|
return
|
||||||
channel = self.bot.get_channel(channel_id)
|
channel = self.bot.get_channel(channel_id)
|
||||||
if channel:
|
if channel:
|
||||||
await channel.edit(name=f'Strinova Players From Steam: {count:,}')
|
await channel.edit(name=f'Strinova Players From Steam: {count:,}')
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ class SysInfo(commands.Cog):
|
|||||||
async with session.get('https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=1282270') as r:
|
async with session.get('https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid=1282270') as r:
|
||||||
data = await r.json()
|
data = await r.json()
|
||||||
count = data['response']['player_count']
|
count = data['response']['player_count']
|
||||||
|
if not isinstance(count, int) or count <= 0:
|
||||||
|
await ctx.reply('🎮 Strinova — Steam is not returning a player count right now, try again in a bit')
|
||||||
|
return
|
||||||
await ctx.reply(f'🎮 Strinova — {count:,} players online right now')
|
await ctx.reply(f'🎮 Strinova — {count:,} players online right now')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,18 @@
|
|||||||
"""firebase_website — Bot → Firestore sync using the Admin SDK.
|
"""firebase_website — Bot → Firestore access using the Admin SDK.
|
||||||
|
|
||||||
Requires GOOGLE_APPLICATION_CREDENTIALS env var pointing to a service-account JSON.
|
Requires GOOGLE_APPLICATION_CREDENTIALS env var pointing to a service-account JSON.
|
||||||
|
|
||||||
This package is loaded as a Discord extension purely for lifecycle hooks
|
Loaded as a Discord extension only so missing credentials fail at startup.
|
||||||
(setup / teardown). It does not register a Cog — it starts a background
|
The calendar and bucketlist cogs use get_db().
|
||||||
SysInfoSync task instead.
|
|
||||||
|
|
||||||
Usage from main.py:
|
Usage from main.py:
|
||||||
await bot.load_extension("firebase_website")
|
await bot.load_extension("firebase_website")
|
||||||
"""
|
"""
|
||||||
######################################################################
|
######################################################################
|
||||||
import logging
|
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from .config import get_db # noqa: re‑export for calendar, bucketlist, etc.
|
from .config import get_db # noqa: re‑export for calendar, bucketlist, etc.
|
||||||
from .sysinfo_sync import SysInfoSync
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
_sync: SysInfoSync | None = None
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot) -> None:
|
async def setup(bot: commands.Bot) -> None:
|
||||||
global _sync
|
|
||||||
get_db() # fail fast if credentials are missing
|
get_db() # fail fast if credentials are missing
|
||||||
_sync = SysInfoSync()
|
|
||||||
_sync.start()
|
|
||||||
|
|
||||||
|
|
||||||
async def teardown(bot: commands.Bot) -> None:
|
|
||||||
global _sync
|
|
||||||
if _sync is not None:
|
|
||||||
await _sync.stop()
|
|
||||||
@@ -8,7 +8,6 @@ from google.cloud.firestore import AsyncClient
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
PROJECT_ID = "esef-514bf"
|
PROJECT_ID = "esef-514bf"
|
||||||
SYSINFO_INTERVAL = 10 # seconds between pushes
|
|
||||||
|
|
||||||
_db: AsyncClient | None = None
|
_db: AsyncClient | None = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
"""Push system stats (CPU, RAM, disk, uptime) to Firestore via Admin SDK."""
|
|
||||||
######################################################################
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
from google.cloud import firestore
|
|
||||||
|
|
||||||
from .config import get_db, SYSINFO_INTERVAL
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
FASTFETCH_CACHE_SECS = 300 # refresh fastfetch every 5 minutes
|
|
||||||
|
|
||||||
VIRTUAL_NIC_PREFIXES = ("lo", "docker", "br-", "veth", "tailscale", "tun", "wg", "virbr")
|
|
||||||
|
|
||||||
|
|
||||||
def _fmt_size(b: int) -> str:
|
|
||||||
"""Bytes → human-readable string (auto-scales B → TB)."""
|
|
||||||
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
||||||
if abs(b) < 1024:
|
|
||||||
return f"{b:.1f} {unit}"
|
|
||||||
b /= 1024
|
|
||||||
return f"{b:.1f} PB"
|
|
||||||
|
|
||||||
|
|
||||||
def _fmt_duration(seconds: int) -> str:
|
|
||||||
days, r = divmod(seconds, 86400)
|
|
||||||
hours, r = divmod(r, 3600)
|
|
||||||
minutes, _ = divmod(r, 60)
|
|
||||||
return f"{days}d {hours}h {minutes}m" if days else f"{hours}h {minutes}m"
|
|
||||||
|
|
||||||
|
|
||||||
class SysInfoSync:
|
|
||||||
"""Background task: collect system stats and push to Firestore."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._task: asyncio.Task | None = None
|
|
||||||
self._fastfetch: str = ""
|
|
||||||
self._fastfetch_ts: float = 0
|
|
||||||
|
|
||||||
# ── public ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def start(self) -> None:
|
|
||||||
if self._task is not None:
|
|
||||||
return
|
|
||||||
self._task = asyncio.create_task(self._run())
|
|
||||||
log.info("SysInfo → Firestore sync started (every %ds)", SYSINFO_INTERVAL)
|
|
||||||
|
|
||||||
async def stop(self) -> None:
|
|
||||||
if self._task is None:
|
|
||||||
return
|
|
||||||
self._task.cancel()
|
|
||||||
self._task = None
|
|
||||||
log.info("SysInfo → Firestore sync stopped")
|
|
||||||
# turns the dot on the website red
|
|
||||||
try:
|
|
||||||
await get_db().collection("sysinfo").document("server").set(
|
|
||||||
{"online": False, "updatedAt": firestore.SERVER_TIMESTAMP}, merge=True)
|
|
||||||
except Exception:
|
|
||||||
log.warning("Could not mark sysinfo offline", exc_info=True)
|
|
||||||
|
|
||||||
# ── loop ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async def _run(self) -> None:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
await self._push()
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
log.warning("SysInfo push failed", exc_info=True)
|
|
||||||
await asyncio.sleep(SYSINFO_INTERVAL)
|
|
||||||
|
|
||||||
# ── push ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async def _push(self) -> None:
|
|
||||||
stats = await self._collect_stats()
|
|
||||||
fetch = await self._get_fastfetch()
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"online": True,
|
|
||||||
"cpu": stats["cpu"],
|
|
||||||
"ram": stats["ram"],
|
|
||||||
"ramUsed": stats["ram_used"],
|
|
||||||
"ramTotal": stats["ram_total"],
|
|
||||||
"disk": stats["disk"],
|
|
||||||
"diskUsed": stats["disk_used"],
|
|
||||||
"diskTotal": stats["disk_total"],
|
|
||||||
"swap": stats["swap"],
|
|
||||||
"swapUsed": stats["swap_used"],
|
|
||||||
"swapTotal": stats["swap_total"],
|
|
||||||
"uptime": stats["uptime"],
|
|
||||||
"botUptime": stats["bot_uptime"],
|
|
||||||
"processes": stats["processes"],
|
|
||||||
"load1": stats["load1"],
|
|
||||||
"load5": stats["load5"],
|
|
||||||
"load15": stats["load15"],
|
|
||||||
"netSent": stats["net_sent"],
|
|
||||||
"netRecv": stats["net_recv"],
|
|
||||||
"fetch": fetch,
|
|
||||||
"updatedAt": firestore.SERVER_TIMESTAMP,
|
|
||||||
}
|
|
||||||
|
|
||||||
db = get_db()
|
|
||||||
await db.collection("sysinfo").document("server").set(payload, merge=True)
|
|
||||||
|
|
||||||
# ── collectors ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async def _collect_stats(self) -> dict:
|
|
||||||
"""Gather all psutil stats; offloads blocking calls to a thread."""
|
|
||||||
cpu = await asyncio.to_thread(psutil.cpu_percent, interval=1)
|
|
||||||
mem = psutil.virtual_memory()
|
|
||||||
disk = psutil.disk_usage("/")
|
|
||||||
swap = psutil.swap_memory()
|
|
||||||
load1, load5, load15 = psutil.getloadavg()
|
|
||||||
nics = psutil.net_io_counters(pernic=True)
|
|
||||||
physical = [c for name, c in nics.items() if not name.startswith(VIRTUAL_NIC_PREFIXES)]
|
|
||||||
net_sent = sum(c.bytes_sent for c in physical)
|
|
||||||
net_recv = sum(c.bytes_recv for c in physical)
|
|
||||||
now = time.time()
|
|
||||||
uptime_seconds = int(now - psutil.boot_time())
|
|
||||||
bot_uptime_seconds = int(now - psutil.Process().create_time())
|
|
||||||
|
|
||||||
return {
|
|
||||||
"cpu": round(cpu, 1),
|
|
||||||
"ram": round(mem.percent, 1),
|
|
||||||
"ram_used": _fmt_size(mem.used),
|
|
||||||
"ram_total": _fmt_size(mem.total),
|
|
||||||
"disk": round(disk.percent, 1),
|
|
||||||
"disk_used": _fmt_size(disk.used),
|
|
||||||
"disk_total": _fmt_size(disk.total),
|
|
||||||
"swap": round(swap.percent, 1),
|
|
||||||
"swap_used": _fmt_size(swap.used),
|
|
||||||
"swap_total": _fmt_size(swap.total),
|
|
||||||
"uptime": _fmt_duration(uptime_seconds),
|
|
||||||
"bot_uptime": _fmt_duration(bot_uptime_seconds),
|
|
||||||
"processes": len(psutil.pids()),
|
|
||||||
"load1": round(load1, 1),
|
|
||||||
"load5": round(load5, 1),
|
|
||||||
"load15": round(load15, 1),
|
|
||||||
"net_sent": _fmt_size(net_sent),
|
|
||||||
"net_recv": _fmt_size(net_recv),
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _get_fastfetch(self) -> str:
|
|
||||||
"""Return cached fastfetch output; refreshed every 5 minutes."""
|
|
||||||
now = time.monotonic()
|
|
||||||
if self._fastfetch and (now - self._fastfetch_ts) < FASTFETCH_CACHE_SECS:
|
|
||||||
return self._fastfetch
|
|
||||||
|
|
||||||
try:
|
|
||||||
proc = await asyncio.create_subprocess_exec(
|
|
||||||
"fastfetch", "--pipe", "--logo", "none",
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
|
||||||
clean = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", stdout.decode())
|
|
||||||
lines = [line for line in clean.strip().splitlines() if line.strip()]
|
|
||||||
self._fastfetch = "\n".join(lines[:15])
|
|
||||||
self._fastfetch_ts = now
|
|
||||||
except Exception:
|
|
||||||
if not self._fastfetch:
|
|
||||||
self._fastfetch = ""
|
|
||||||
return self._fastfetch
|
|
||||||
Reference in new issue
Block a user