fix: ttt/triviaduel crash, blackjack per-player, improve hottake/nhie prompts
- Import MATRIX_USER_ID in commands.py (was missing — caused !ttt and !triviaduel to crash with NameError on every invocation) - Blackjack is now per-player per-room: multiple players can each run their own game simultaneously; !hit and !stand operate on the caller's own game only - !hottake: pick a random topic from 20 categories and pass it to the model so takes aren't all nostalgia-flavoured - !nhie: tighter prompt with topic rotation and a word-count cap so generated scenarios are simpler and more relatable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+55
-36
@@ -18,7 +18,7 @@ from config import (
|
|||||||
MAX_DICE_SIDES, MAX_DICE_COUNT, BOT_PREFIX, ADMIN_USERS,
|
MAX_DICE_SIDES, MAX_DICE_COUNT, BOT_PREFIX, ADMIN_USERS,
|
||||||
OLLAMA_URL, OLLAMA_MODEL, CREATIVE_MODEL, ASK_MODEL, COOLDOWN_SECONDS,
|
OLLAMA_URL, OLLAMA_MODEL, CREATIVE_MODEL, ASK_MODEL, COOLDOWN_SECONDS,
|
||||||
MINECRAFT_RCON_HOST, MINECRAFT_RCON_PORT, MINECRAFT_RCON_PASSWORD,
|
MINECRAFT_RCON_HOST, MINECRAFT_RCON_PORT, MINECRAFT_RCON_PASSWORD,
|
||||||
RCON_TIMEOUT, MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH,
|
RCON_TIMEOUT, MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, MATRIX_USER_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger("matrixbot")
|
logger = logging.getLogger("matrixbot")
|
||||||
@@ -2925,13 +2925,23 @@ def record_nhie_reaction(event_id: str, sender: str, key: str) -> None:
|
|||||||
poll["have"].discard(sender)
|
poll["have"].discard(sender)
|
||||||
|
|
||||||
|
|
||||||
|
_NHIE_TOPICS = [
|
||||||
|
"travel", "food", "social situations", "school or work", "technology",
|
||||||
|
"outdoor adventures", "relationships", "embarrassing moments",
|
||||||
|
"sleep habits", "gaming or movies", "sports", "shopping",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def _generate_nhie_prompt() -> str | None:
|
async def _generate_nhie_prompt() -> str | None:
|
||||||
|
topic = random.choice(_NHIE_TOPICS)
|
||||||
system_msg = (
|
system_msg = (
|
||||||
"Generate a fun, surprising, or relatable 'Never Have I Ever' statement. "
|
"You write Never Have I Ever statements for a party game. Rules: "
|
||||||
"Do NOT include 'Never have I ever' in your response — just the action part. "
|
"1) Return ONLY the action — do NOT write 'Never have I ever'. "
|
||||||
"Keep it funny, PG-13 at most, and something that creates an interesting mix of have/have-not. "
|
"2) Keep it simple and realistic — something an average person might actually have done. "
|
||||||
"One sentence only, no quotes."
|
"3) Short: under 12 words. "
|
||||||
|
"4) PG-13 at most. No quotes. No explanation."
|
||||||
)
|
)
|
||||||
|
user_msg = f"Write a Never Have I Ever statement about: {topic}"
|
||||||
try:
|
try:
|
||||||
timeout = aiohttp.ClientTimeout(total=30)
|
timeout = aiohttp.ClientTimeout(total=30)
|
||||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||||
@@ -2939,7 +2949,7 @@ async def _generate_nhie_prompt() -> str | None:
|
|||||||
f"{OLLAMA_URL}/api/chat",
|
f"{OLLAMA_URL}/api/chat",
|
||||||
json={"model": CREATIVE_MODEL, "stream": False,
|
json={"model": CREATIVE_MODEL, "stream": False,
|
||||||
"messages": [{"role": "system", "content": system_msg},
|
"messages": [{"role": "system", "content": system_msg},
|
||||||
{"role": "user", "content": "Generate a Never Have I Ever statement."}]},
|
{"role": "user", "content": user_msg}]},
|
||||||
) as response:
|
) as response:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
text = data.get("message", {}).get("content", "").strip().strip('"')
|
text = data.get("message", {}).get("content", "").strip().strip('"')
|
||||||
@@ -3015,12 +3025,28 @@ def record_hottake_reaction(event_id: str, sender: str, key: str) -> None:
|
|||||||
poll["agree"].discard(sender)
|
poll["agree"].discard(sender)
|
||||||
|
|
||||||
|
|
||||||
|
_HOTTAKE_TOPICS = [
|
||||||
|
"food and cooking", "music genres", "social media and technology",
|
||||||
|
"sports and fitness", "video games", "movies and TV shows",
|
||||||
|
"work and career culture", "fashion and style", "travel and tourism",
|
||||||
|
"pets and animals", "relationships and dating", "education and school",
|
||||||
|
"sleep and daily habits", "outdoor activities", "city vs rural living",
|
||||||
|
"coffee and caffeine", "cars and driving", "reading and books",
|
||||||
|
"money and spending habits", "home and interior design",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def _generate_hot_take() -> str | None:
|
async def _generate_hot_take() -> str | None:
|
||||||
|
topic = random.choice(_HOTTAKE_TOPICS)
|
||||||
system_msg = (
|
system_msg = (
|
||||||
"Generate a spicy, controversial hot take opinion. It should be something where "
|
"You generate short, spicy hot take opinions. Rules: "
|
||||||
"reasonable people strongly disagree — not hateful, but genuinely polarising. "
|
"1) ONE sentence only — no more. "
|
||||||
"Keep it fun and debate-worthy. One sentence only, stated as a confident opinion. No quotes."
|
"2) State it as a confident, direct opinion. "
|
||||||
|
"3) It must be genuinely controversial — people should strongly disagree. "
|
||||||
|
"4) Do NOT mention nostalgia, pop culture legacy, or historical impact. "
|
||||||
|
"5) No quotes around your response. No preamble. Just the hot take."
|
||||||
)
|
)
|
||||||
|
user_msg = f"Give me a hot take about: {topic}"
|
||||||
try:
|
try:
|
||||||
timeout = aiohttp.ClientTimeout(total=30)
|
timeout = aiohttp.ClientTimeout(total=30)
|
||||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||||
@@ -3028,7 +3054,7 @@ async def _generate_hot_take() -> str | None:
|
|||||||
f"{OLLAMA_URL}/api/chat",
|
f"{OLLAMA_URL}/api/chat",
|
||||||
json={"model": CREATIVE_MODEL, "stream": False,
|
json={"model": CREATIVE_MODEL, "stream": False,
|
||||||
"messages": [{"role": "system", "content": system_msg},
|
"messages": [{"role": "system", "content": system_msg},
|
||||||
{"role": "user", "content": "Give me a hot take."}]},
|
{"role": "user", "content": user_msg}]},
|
||||||
) as response:
|
) as response:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
text = data.get("message", {}).get("content", "").strip().strip('"')
|
text = data.get("message", {}).get("content", "").strip().strip('"')
|
||||||
@@ -3231,7 +3257,8 @@ async def cmd_move(client: AsyncClient, room_id: str, sender: str, args: str):
|
|||||||
# Blackjack
|
# Blackjack
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|
||||||
_BLACKJACK_GAMES: dict[str, dict] = {}
|
# {room_id: {player_id: game}} — multiple players per room each get their own game
|
||||||
|
_BLACKJACK_GAMES: dict[str, dict[str, dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
def _bj_new_deck() -> list:
|
def _bj_new_deck() -> list:
|
||||||
@@ -3293,9 +3320,9 @@ def _bj_board(game: dict, reveal_dealer: bool = False, status: str = "") -> tupl
|
|||||||
|
|
||||||
@command("blackjack", "Play Blackjack! Beat the dealer — !hit to draw, !stand to stay")
|
@command("blackjack", "Play Blackjack! Beat the dealer — !hit to draw, !stand to stay")
|
||||||
async def cmd_blackjack(client: AsyncClient, room_id: str, sender: str, args: str):
|
async def cmd_blackjack(client: AsyncClient, room_id: str, sender: str, args: str):
|
||||||
if room_id in _BLACKJACK_GAMES:
|
room_games = _BLACKJACK_GAMES.get(room_id, {})
|
||||||
g = _BLACKJACK_GAMES[room_id]
|
if sender in room_games:
|
||||||
plain, html = _bj_board(g)
|
plain, html = _bj_board(room_games[sender])
|
||||||
await send_html(client, room_id, plain, html)
|
await send_html(client, room_id, plain, html)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3309,13 +3336,12 @@ async def cmd_blackjack(client: AsyncClient, room_id: str, sender: str, args: st
|
|||||||
"player_id": sender,
|
"player_id": sender,
|
||||||
"board_event_id": None,
|
"board_event_id": None,
|
||||||
}
|
}
|
||||||
_BLACKJACK_GAMES[room_id] = game
|
_BLACKJACK_GAMES.setdefault(room_id, {})[sender] = game
|
||||||
|
|
||||||
# Check for instant blackjack
|
# Check for instant blackjack
|
||||||
if _bj_total(player_hand) == 21:
|
if _bj_total(player_hand) == 21:
|
||||||
del _BLACKJACK_GAMES[room_id]
|
del _BLACKJACK_GAMES[room_id][sender]
|
||||||
plain, html = _bj_board(game, reveal_dealer=True,
|
plain, html = _bj_board(game, reveal_dealer=True, status="🎉 BLACKJACK! You win!")
|
||||||
status="🎉 BLACKJACK! You win!")
|
|
||||||
await send_html(client, room_id, plain, html)
|
await send_html(client, room_id, plain, html)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3337,12 +3363,9 @@ async def _bj_update(client: AsyncClient, room_id: str, game: dict,
|
|||||||
|
|
||||||
@command("hit", "Draw another card in Blackjack")
|
@command("hit", "Draw another card in Blackjack")
|
||||||
async def cmd_hit(client: AsyncClient, room_id: str, sender: str, args: str):
|
async def cmd_hit(client: AsyncClient, room_id: str, sender: str, args: str):
|
||||||
if room_id not in _BLACKJACK_GAMES:
|
game = _BLACKJACK_GAMES.get(room_id, {}).get(sender)
|
||||||
await send_text(client, room_id, "No Blackjack game active. Start one with !blackjack")
|
if not game:
|
||||||
return
|
await send_text(client, room_id, "You don't have an active Blackjack game. Start one with !blackjack")
|
||||||
game = _BLACKJACK_GAMES[room_id]
|
|
||||||
if sender != game["player_id"]:
|
|
||||||
await send_text(client, room_id, "It's not your game!")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
card = game["deck"].pop() if game["deck"] else _bj_new_deck().pop()
|
card = game["deck"].pop() if game["deck"] else _bj_new_deck().pop()
|
||||||
@@ -3350,20 +3373,19 @@ async def cmd_hit(client: AsyncClient, room_id: str, sender: str, args: str):
|
|||||||
total = _bj_total(game["player_hand"])
|
total = _bj_total(game["player_hand"])
|
||||||
|
|
||||||
if total > 21:
|
if total > 21:
|
||||||
del _BLACKJACK_GAMES[room_id]
|
del _BLACKJACK_GAMES[room_id][sender]
|
||||||
await _bj_update(client, room_id, game, reveal=True,
|
await _bj_update(client, room_id, game, reveal=True,
|
||||||
status=f"💀 Bust! You went over 21 with {total}. Dealer wins.")
|
status=f"💀 Bust! You went over 21 with {total}. Dealer wins.")
|
||||||
elif total == 21:
|
elif total == 21:
|
||||||
# Auto-stand on 21
|
|
||||||
await _bj_update(client, room_id, game, status="Hit 21! Standing automatically...")
|
await _bj_update(client, room_id, game, status="Hit 21! Standing automatically...")
|
||||||
await _auto_stand(client, room_id, game)
|
await _auto_stand(client, room_id, sender, game)
|
||||||
else:
|
else:
|
||||||
await _bj_update(client, room_id, game)
|
await _bj_update(client, room_id, game)
|
||||||
|
|
||||||
|
|
||||||
async def _auto_stand(client: AsyncClient, room_id: str, game: dict):
|
async def _auto_stand(client: AsyncClient, room_id: str, player_id: str, game: dict):
|
||||||
"""Dealer plays out and resolve the game."""
|
"""Dealer plays out and resolve the game."""
|
||||||
_BLACKJACK_GAMES.pop(room_id, None)
|
_BLACKJACK_GAMES.get(room_id, {}).pop(player_id, None)
|
||||||
dealer_hand = game["dealer_hand"]
|
dealer_hand = game["dealer_hand"]
|
||||||
deck = game["deck"]
|
deck = game["deck"]
|
||||||
while _bj_total(dealer_hand) < 17:
|
while _bj_total(dealer_hand) < 17:
|
||||||
@@ -3386,14 +3408,11 @@ async def _auto_stand(client: AsyncClient, room_id: str, game: dict):
|
|||||||
|
|
||||||
@command("stand", "Stand in Blackjack — dealer plays out")
|
@command("stand", "Stand in Blackjack — dealer plays out")
|
||||||
async def cmd_stand(client: AsyncClient, room_id: str, sender: str, args: str):
|
async def cmd_stand(client: AsyncClient, room_id: str, sender: str, args: str):
|
||||||
if room_id not in _BLACKJACK_GAMES:
|
game = _BLACKJACK_GAMES.get(room_id, {}).get(sender)
|
||||||
await send_text(client, room_id, "No Blackjack game active. Start one with !blackjack")
|
if not game:
|
||||||
|
await send_text(client, room_id, "You don't have an active Blackjack game. Start one with !blackjack")
|
||||||
return
|
return
|
||||||
game = _BLACKJACK_GAMES[room_id]
|
await _auto_stand(client, room_id, sender, game)
|
||||||
if sender != game["player_id"]:
|
|
||||||
await send_text(client, room_id, "It's not your game!")
|
|
||||||
return
|
|
||||||
await _auto_stand(client, room_id, game)
|
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user