Skip to content

Add remote stats + player functionality via BisectHosting StarbaseAPI#1

Open
ImKringle wants to merge 7 commits intoMinidoracat:mainfrom
ImKringle:starbase-api
Open

Add remote stats + player functionality via BisectHosting StarbaseAPI#1
ImKringle wants to merge 7 commits intoMinidoracat:mainfrom
ImKringle:starbase-api

Conversation

@ImKringle
Copy link

Saw this in the Discord for HumanitZ and figured I'd add a method for the Bisect users to use this as well, as the Players/Stats are able to be derived from specific endpoints at https://games.bisecthosting.com/docs :

  • Introduces optional Bisect Starbase API support to complement RCON-based status for HumanitZ.
  • Enables Bisect users to run the bot without local file access to PlayerConnectedLog.txt and to display Bisect-reported resource stats.

Changes:

  • Config
    • Add STARBASE_ID and STARBASE_TOKEN optional env vars.
    • Document how to obtain STARBASE_ID (from panel URL) and STARBASE_TOKEN (Account → API Credentials) in README.
  • Player tracking
    • If STARBASE_* is set, fetch HumanitZServer/PlayerConnectedLog.txt from Bisect via /api/client/servers/{id}/files/contents and parse it to derive per-player online durations.
    • Fallback to local PlayerConnectedLog.txt when Starbase config is absent.
  • System resources
    • When STARBASE_* is set and SHOW_SYSTEM_STATS is true, query Bisect /resources to populate CPU/memory/disk/network in the status embed instead of local psutil as this requires access locally for the bot.
    • Preserve existing psutil-based stats when Starbase is not configured.

Notes:

  • Bisect endpoints used: files/contents (POST) for PlayerConnectedLog and resources (GET) for live usage.
  • If Starbase returns 405/authorization errors, the bot logs and gracefully falls back.
  • No impact on users who don’t configure STARBASE_*; defaults remain unchanged.

Testing:

  • Verified normal operation with only RCON (no Starbase).
  • With valid STARBASE_ID/TOKEN, confirmed:
    • Remote log retrieval and player duration rendering.
    • Resource panel shows values from Bisect /resources.
  • Handled empty/missing logs and transient HTTP errors without crashing.

Apologies if any comments for ZH are inaccurate, not fluent so AI handled those. Feel free to fix them so they actually read accurately 😅

@gemini-code-assist
Copy link

Summary of Changes

Hello @ImKringle, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the bot's flexibility by introducing an optional integration with the Bisect Hosting Starbase API. This allows the bot to operate more effectively in environments where direct local file access or system resource monitoring might be restricted, such as on managed game hosting platforms. By leveraging the Bisect API, the bot can now remotely retrieve player connection logs and server resource metrics, providing a more versatile and robust solution for server status reporting.

Highlights

  • Bisect Starbase API Integration: Introduced optional support for Bisect Hosting's Starbase API to fetch remote server statistics and player tracking data.
  • Remote Player Log Retrieval: Enabled fetching the HumanitZServer/PlayerConnectedLog.txt file remotely via the Bisect API, eliminating the need for local file access for player online duration tracking.
  • Remote System Resource Monitoring: Implemented the ability to display server CPU, memory, disk, and network usage directly from the Bisect /resources API, as an alternative to local psutil monitoring.
  • Configuration: Added new optional environment variables, STARBASE_ID and STARBASE_TOKEN, to configure the Bisect API integration, along with updated documentation.
  • Robustness: Ensured graceful fallback to existing local methods if Starbase API credentials are not provided or if API calls encounter errors.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .env.example
    • Added STARBASE_ID and STARBASE_TOKEN environment variables with detailed descriptions.
  • .env.example.zh-TW
    • Added STARBASE_ID and STARBASE_TOKEN environment variables with Traditional Chinese descriptions.
  • README.md
    • Updated the environment variable table to include STARBASE_ID and STARBASE_TOKEN with their descriptions.
  • README.zh-TW.md
    • Updated the Traditional Chinese environment variable table to include STARBASE_ID and STARBASE_TOKEN.
  • src/humanitz_bot/cogs/server_status.py
    • Modified the constructor to initialize PlayerTracker with an optional remote fetcher for player logs if Starbase credentials are provided.
    • Introduced a flag to conditionally use Starbase API for system resource stats.
    • Updated the status update loop to fetch system resources from Bisect API or local psutil based on configuration.
    • Modified the embed building method to accept and prioritize Starbase resource data.
    • Added new methods _format_starbase_resources and _fetch_starbase_resources to handle Bisect API data formatting and retrieval.
    • Refactored logger info and message update calls for conciseness.
  • src/humanitz_bot/config.py
    • Removed an unused import for Path.
    • Added starbase_token and starbase_id as optional attributes to the Settings dataclass.
    • Updated the from_env method to load STARBASE_TOKEN and STARBASE_ID from environment variables.
  • src/humanitz_bot/services/player_tracker.py
    • Imported necessary types and the requests library for remote fetching.
    • Modified the PlayerTracker constructor to accept an optional starbase_fetcher callable.
    • Implemented _read_local_tail for reading local log files and _read_remote_full for fetching logs via the Starbase API.
    • Updated get_online_times to dynamically choose between local or remote log sources based on the presence of a starbase_fetcher.
    • Removed an unnecessary comment regarding date parsing.
Activity
  • The author verified normal operation with only RCON (no Starbase).
  • The author confirmed remote log retrieval and player duration rendering with valid STARBASE_ID/TOKEN.
  • The author confirmed resource panel shows values from Bisect /resources with valid STARBASE_ID/TOKEN.
  • The author handled empty/missing logs and transient HTTP errors without crashing during testing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds optional integration with the BisectHosting Starbase API to fetch server statistics and player logs remotely. However, a potential Denial of Service (DoS) vulnerability was identified in the way remote log files are handled, as the bot currently downloads and processes entire log files in memory, which could lead to memory exhaustion. Additionally, the implementation could benefit from improvements in maintainability by removing code duplication and hardcoded values, and enhanced robustness through more specific exception handling.

Comment on lines +131 to +132
except Exception:
starbase_stats = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Catching a broad Exception can hide unexpected errors and make debugging difficult. It's better to catch more specific exceptions that you expect from the network request, such as requests.exceptions.RequestException. This will prevent the bot from silently ignoring other unrelated errors. This also applies to the try...except block in _format_starbase_resources on line 296.

Comment on lines +50 to +61
def _read_remote_full(self) -> list[str]:
"""遠端抓取完整檔案內容(Starbase),並回傳行列表。"""
try:
raw = self._starbase_fetcher() if self._starbase_fetcher else ""
except Exception as e:
logger.error("Failed to fetch remote PlayerConnectedLog via Starbase: %s", e)
return []
if not raw:
return []
# 直接解析全檔內容,不做 tail(避免遺漏)
normalized = raw.replace("\r\n", "\n").replace("\r", "\n")
return [line for line in normalized.split("\n") if line]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The _read_remote_full method currently fetches and processes entire PlayerConnectedLog.txt files into memory. This poses a significant Denial of Service (DoS) vulnerability, as large log files could lead to memory exhaustion and bot crashes, especially given the frequent update cycle. An attacker could exacerbate this by rapidly increasing log size. It is crucial to implement a more memory-efficient approach, such as using an API endpoint that supports tailing or range requests, or processing the log file via streaming to avoid loading the entire content into memory. Furthermore, the current broad Exception catch can mask specific network-related issues; consider handling more specific exceptions like requests.exceptions.RequestException for robust error management.

server_id = settings.starbase_id

def _fetch_remote_log() -> str:
url = f"https://games.bisecthosting.com/api/client/servers/{server_id}/files/contents"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Starbase API URL is hardcoded here, and its base is used again on line 316. To improve maintainability and avoid "magic strings", consider defining the base URL https://games.bisecthosting.com/api/client/servers as a module-level constant.

def _fetch_remote_log() -> str:
url = f"https://games.bisecthosting.com/api/client/servers/{server_id}/files/contents"
headers = {"Authorization": f"Bearer {token}"}
params = {"file": "HumanitZServer/PlayerConnectedLog.txt"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The remote log file path HumanitZServer/PlayerConnectedLog.txt is hardcoded. It would be better to define this as a module-level constant to improve maintainability and avoid magic strings.

url = f"https://games.bisecthosting.com/api/client/servers/{server_id}/files/contents"
headers = {"Authorization": f"Bearer {token}"}
params = {"file": "HumanitZServer/PlayerConnectedLog.txt"}
import requests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requests library is imported locally within this function, and again on line 318. It's a best practice in Python to place all imports at the top of the file. This improves code readability, makes dependencies clear, and avoids repeated import overhead.

Comment on lines +90 to +92
self._use_starbase_stats: bool = bool(
getattr(settings, "starbase_token", None) and getattr(settings, "starbase_id", None)
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic to check if Starbase is configured is also present on line 52. To avoid code duplication and improve maintainability, this check should be performed only once in the __init__ method, with the result stored in self._use_starbase_stats. This attribute can then be used in both places.

Comment on lines +299 to +300
mem_str = f"{mem_bytes/1024/1024/1024:.2f} GB"
disk_str = f"{disk_bytes/1024/1024/1024:.2f} GB"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value for converting bytes to gigabytes is calculated inline using division. To improve readability and maintainability, it's better to use exponentiation (1024**3) and ideally define this as a constant (e.g., _BYTES_PER_GB = 1024**3) at the module level.

Suggested change
mem_str = f"{mem_bytes/1024/1024/1024:.2f} GB"
disk_str = f"{disk_bytes/1024/1024/1024:.2f} GB"
mem_str = f"{mem_bytes / (1024**3):.2f} GB"
disk_str = f"{disk_bytes / (1024**3):.2f} GB"

@Minidoracat
Copy link
Owner

Hey @ImKringle, thanks for putting this together! The Starbase API integration is a great idea — it fills a real gap for Bisect users who don't have local file access. The docs updates (both languages!) and backward compatibility are appreciated.

I've done a thorough review and have some feedback below. I've split it into must-fix items (blocking merge) and suggestions (nice-to-have, can be addressed later).


🔴 Must Fix (Blocking)

1. requests not declared in pyproject.toml
The PR imports requests but never adds it to dependencies. This will cause ModuleNotFoundError on any fresh install.
→ Add "requests>=2.32" to pyproject.toml dependencies.

2. assert used as None guard in production code

# server_status.py — _fetch_starbase_resources()
assert settings.starbase_token and settings.starbase_id

Python's -O flag strips all assert statements, which would send Bearer None to the API. Please replace with:

if not settings.starbase_token or not settings.starbase_id:
    raise RuntimeError("Starbase credentials not configured")

3. Silent exception swallowing (no logging)

except Exception:
    starbase_stats = None  # completely silent

When the Starbase API returns 401/403/500/timeout, there's zero indication in logs. This makes debugging nearly impossible.
→ At minimum: logger.warning("Starbase resources fetch failed: %s", e)
→ Ideally: catch requests.exceptions.RequestException instead of bare Exception.

4. Unused imports in player_tracker.py

from typing import Callable, Awaitable, Optional  # Awaitable is unused
import requests  # never used in this file

→ Remove Awaitable and requests. Only Callable is needed.

5. import requests inside function bodies
In server_status.py, requests is imported inside _fetch_remote_log() and _fetch_starbase_resources(). Python convention (and this project's pattern) is to place imports at the top of the file.

6. Optional[Callable]Callable | None
This project uses Python 3.10+ union syntax throughout. Please change:

# Before
starbase_fetcher: Optional[Callable[[], str]] = None
# After
starbase_fetcher: Callable[[], str] | None = None

And you can drop the from typing import Optional import entirely.

7. getattr() unnecessary on own dataclass fields

if getattr(settings, "starbase_token", None) and getattr(settings, "starbase_id", None):

Since you've added starbase_token and starbase_id to the Settings dataclass, they're guaranteed to exist. Simplify to:

if settings.starbase_token and settings.starbase_id:

8. Starbase tokens should have placeholder validation
Existing required fields (DISCORD_TOKEN, RCON_PASSWORD) go through _is_placeholder() to reject values like YOUR_TOKEN_HERE. The Starbase credentials skip this check. A user who copies .env.example without editing would silently get auth failures.


💡 Suggestions (Non-blocking)

These are improvements I'd recommend but they don't need to be in this PR:

  • Hardcoded API URLs/paths → Consider extracting https://games.bisecthosting.com/api/client/servers and HumanitZServer/PlayerConnectedLog.txt into module-level constants (_STARBASE_API_BASE, _REMOTE_LOG_PATH).

  • Full remote log download every 30s → As the log grows over time this could get expensive. A TTL cache or response size limit would help. Not urgent for v1 though.

  • Starbase stats format differs from psutil format → The local version shows progress bars for CPU/memory/disk plus uptime. The Starbase version only has a CPU bar and shows cumulative network bytes instead of per-second rates. Worth aligning in a follow-up.

  • No log message on Starbase activation → A simple logger.info("Starbase API enabled (server_id=%s)", settings.starbase_id) would help users confirm it's working.

  • Unrelated formatting changes → There are ~10 spots where multi-line expressions were collapsed to single lines. These make the diff harder to review. In the future, cosmetic changes are best submitted as a separate PR.


Overall this is a solid first contribution with a clear use case. Once the blocking items above are addressed, I'd be happy to merge. Let me know if you have any questions! 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants