How the HoshinoBot Permission System Manages User Privileges in priv.py
The HoshinoBot permission system uses integer-based privilege levels defined in hoshino/priv.py to enforce access control, evaluating super-users, temporary block lists, static configuration lists, and dynamic group roles through the get_user_priv() and check_priv() functions.
HoshinoBot implements a lightweight yet robust permission model centered in hoshino/priv.py. This module assigns every user a numeric privilege level that determines which commands they can execute, balancing transient bans, permanent blacklists, and real-time group membership roles. The system is designed to be both efficient and flexible, allowing module developers to guard commands with simple integer comparisons.
Privilege Constants and Numeric Levels
The permission hierarchy is encoded as integer constants ranging from -999 (blocked) to 999 (super-user). These values are declared at the top of [hoshino/priv.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/priv.py#L12-L21):
| Constant | Value | Meaning |
|---|---|---|
BLACK |
-999 |
Blocked user (temporary or permanent ban) |
DEFAULT |
0 |
Unset or fallback level |
NORMAL |
1 |
Regular group member |
PRIVATE |
10 |
Private chat (non-group context) |
ADMIN |
21 |
Group administrator |
OWNER |
22 |
Group owner |
WHITE |
51 |
Whitelisted user (exempt from restrictions) |
SUPERUSER / SU |
999 |
Bot owner with full access |
Higher integers indicate greater privileges. When enforcing permissions, the system checks if the user's level is greater than or equal to the required threshold.
Temporary and Static Block Lists
HoshinoBot employs a three-layer defense mechanism combining transient blocks, static configuration lists, and dynamic role detection.
Temporary Blocks
Two dictionaries in priv.py manage time-based bans:
_black_group = {} # {group_id: expiry_datetime}
_black_user = {} # {user_id: expiry_datetime}
The functions set_block_group(group_id, time) and set_block_user(user_id, time) add entries with expiration timestamps (datetime.now() + time). The check functions check_block_group and check_block_user return True while the block is active and automatically purge expired entries. See the implementation in [hoshino/priv.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/priv.py#L23-L49).
Static Configuration Lists
Permanent access control is configured via:
config.BLACK_LIST: A list of user IDs denied all access (e.g.,BLACK_LIST = [1974906693]in [config_example/__bot__.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/config_example/__bot__.py#L13)).config.WHITE_LIST: A list of user IDs granted theWHITElevel (51), exempting them from most restrictions regardless of group role.
These lists are evaluated inside get_user_priv (lines 44-62 of [priv.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/priv.py#L44-L62)).
Determining User Privileges with get_user_priv
The core authorization logic resides in get_user_priv(ev), which inspects a CQEvent and returns the user's integer privilege level. The function evaluates conditions in strict priority order:
def get_user_priv(ev: CQEvent):
uid = ev.user_id
if uid in hoshino.config.SUPERUSERS:
return SUPERUSER
if check_block_user(uid):
return BLACK
if uid in config.WHITE_LIST:
return WHITE
if ev['message_type'] == 'group':
if not ev.anonymous:
role = ev.sender.get('role')
if role == 'member': return NORMAL
elif role == 'admin': return ADMIN
elif role == 'owner': return OWNER
return NORMAL
if ev['message_type'] == 'private':
return PRIVATE
return NORMAL
This implementation is found in [hoshino/priv.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/priv.py#L55-L77). The hierarchy ensures that super-user status overrides all other checks, while temporary blocks take precedence over whitelist status. For group messages, the sender's role (member, admin, or owner) is mapped directly to the corresponding privilege constant.
Enforcing Permissions with check_priv
Once a user's privilege level is determined, command handlers enforce restrictions via check_priv(ev, require):
def check_priv(ev: CQEvent, require: int) -> bool:
if ev['message_type'] == 'group':
return bool(get_user_priv(ev) >= require)
else:
return False
Located in [hoshino/priv.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/priv.py#L80-L85), this function performs a simple integer comparison. It returns True only if the user's level is greater than or equal to the required threshold. Notably, it explicitly returns False for non-group messages, meaning privileged commands cannot be invoked via private chat by default.
Real-World Usage in Modules
Module developers integrate privilege checks by guarding command entry points with priv.check_priv. The pattern is consistent across the codebase: verify the requirement and return early if insufficient.
Super-User Restrictions
In the picfinder module, sensitive operations are restricted to super-users:
if not priv.check_priv(ev, priv.SUPERUSER):
return
See the implementation in [hoshino/modules/picfinder/__init__.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/modules/picfinder/__init__.py#L98).
Admin-Only Commands
The gacha module restricts configuration commands to group administrators:
if not priv.check_priv(ev, priv.ADMIN):
return
This appears in [hoshino/modules/priconne/gacha/__init__.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/modules/priconne/gacha/__init__.py#L68).
Service-Level Integration
The Service class in [hoshino/service.py](https://github.com/ice9coffee/hoshinobot/blob/master/hoshino/service.py#L163) combines privilege checks with feature enablement:
return self.check_enabled(gid) and not priv.check_block_group(gid) and priv.check_priv(ev, self.use_priv)
This ensures that a command is only executed if the service is enabled for the group, the group is not temporarily blocked, and the user meets the required privilege level.
Summary
- Integer-based hierarchy: HoshinoBot assigns every user a numeric level from
-999(BLACK) to999(SUPERUSER), defined inhoshino/priv.py. - Three-layer defense: The system evaluates temporary block lists first, then static configuration lists (
BLACK_LIST,WHITE_LIST), and finally dynamic group roles. - Priority evaluation:
get_user_privchecks super-users, blocks, whitelists, and group roles in strict order, returning the highest applicable level. - Simple enforcement:
check_privcompares the user's level against a required threshold using>=, returningFalsefor private messages by default. - Modular integration: Command handlers across modules like
picfinderandpriconne/gachaguard execution withif not priv.check_priv(ev, priv.LEVEL): return.
Frequently Asked Questions
What is the highest privilege level in HoshinoBot?
The highest level is SUPERUSER (integer 999), defined as the constant SU in hoshino/priv.py. Users whose IDs are listed in config.SUPERUSERS receive this level, overriding all other permission checks including temporary blocks and group roles.
How does HoshinoBot handle temporary user bans?
Temporary bans are managed through the _black_user and _black_group dictionaries in priv.py, which map IDs to expiration timestamps. When set_block_user() or set_block_group() is called with a timedelta, the system stores datetime.now() + time as the expiry. get_user_priv() returns BLACK (-999) while the current time is less than the stored expiry, automatically excluding expired entries during checks.
Can private chat users execute privileged commands?
No. By design, check_priv() explicitly returns False when ev['message_type'] is not 'group'. This means commands requiring priv.NORMAL or higher cannot be triggered via private messages, ensuring that privilege checks are strictly enforced within group contexts where roles (member, admin, owner) are verifiable.
Where are static blacklists and whitelists configured?
Static lists are defined in the bot configuration, typically in hoshino/config/__bot__.py (based on the example in hoshino/config_example/__bot__.py). The BLACK_LIST contains user IDs permanently denied access, while WHITE_LIST contains users granted the WHITE level (51), exempting them from most restrictions regardless of their group role. These lists are evaluated inside get_user_priv() after super-user checks but before group role detection.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →