Files
devplacepy/devplacepy/utils/__init__.py
T
blindxfishandClaude Fable 5 80956ce0f4 Add Opinion Wars: week-long two-faction battles attached to posts
A new post attachment type beside polls: the composer gains a Start
Opinion War builder (same disabled-inputs opt-in as the poll builder)
that names exactly two factions; the battle runs for exactly 7 days
from post creation. Members join a side, may defect at any time
(damage already dealt stays with the faction it was dealt to), and
fight once per 24 hours per battle. A fight spends 25 Code Farm coins
and deals deterministic level-weighted damage: 100 + 10 * min(level,
20) HP, so a newcomer deals 110 and a veteran caps at 300 - no
randomness anywhere.

The battle renders on the post card as a CSS pixel-art battlefield
(box-shadow sprites: castles, faction flags, marching soldiers, a
flickering campfire; steps() animation, disabled under reduced motion)
with live HP bars, a countdown, the viewer's faction strip, top
contributors and an event ticker. Live frames ride pub/sub on
public.battle.{uid} via a relay on the service-lock owner, with the
durable opinion_war_events trail (per-war atomic seq) as the source of
truth and a 15s incremental poller as fallback. /battles lists battles
with active/ended/mine filters, search and pagination.

Every mutation is a conditional UPDATE via conditional_update_row: the
fight sequence claims the cooldown first, then spends coins, then lands
the damage, compensating earlier steps on any later refusal so a crash
costs a turn, never coins. Resolution is lazy on read (no cron):
an exactly-once CAS computes the winner in the statement, awards XP
(participation, winner bonus, top damage dealer bonus; draws pay
participation only), emits the result event and notifies fighters. The
OpinionWarService backstop resolves unviewed wars and sends
fight-ready notifications, exactly-once via a marker CAS.

Fan-out: battle notification type, four badges, audit keys
(battle.create/join/switch/fight/resolve), Devii actions (join/fight
confirm-gated), API docs group, docs prose page, sitemap and topnav
entries, REPORTABLE_TARGETS registration, post-delete cascades,
README and nested CLAUDE.md documentation.

Verified with the four-layer procedure: property checks over the full
damage domain, 1200-step stateful fuzz (hp-sum invariant, coins never
negative, resolved totals frozen), and real 8-process races proving
exactly-once semantics for concurrent fights, double-spends across two
wars, resolution XP and double-joins. Persisted tests in
tests/unit/services/opinionwar, tests/api/battles, tests/e2e/battles
and tests/api/posts/create.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:30:02 +02:00

140 lines
2.8 KiB
Python

# retoor <retoor@molodetz.nl>
import asyncio
import base64
import binascii
import hashlib
import html
import json
import logging
import re
import secrets
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Optional
from urllib.parse import urlsplit
from passlib.hash import pbkdf2_sha256
from fastapi import Request, HTTPException, status
from devplacepy.cache import TTLCache
from devplacepy.database import (
get_table,
get_user_stars,
bump_cache_version,
sync_local_cache,
invalidate_admins_cache,
notification_enabled,
get_silenced_uids,
record_activity,
record_unique_activity,
)
from devplacepy.config import (
SESSION_MAX_AGE,
DEFAULT_CORRECTION_PROMPT,
DEFAULT_MODIFIER_PROMPT,
)
from devplacepy.services.background import background
from devplacepy.utils.passwords import (
hash_password,
verify_password,
hash_password_async,
verify_password_async,
create_session,
)
from devplacepy.utils.authcache import (
_user_cache,
_sync_auth_cache,
clear_user_cache,
clear_session_cache,
)
from devplacepy.utils.auth import (
_UNSET,
_user_from_session,
_user_from_api_key,
_user_from_basic,
_request_has_auth,
_resolve_user,
_user_from_devrant_token,
_user_from_access_token,
get_current_user,
)
from devplacepy.utils.guards import (
safe_next,
redirect_back,
cookie_secure,
client_ip,
require_user,
is_admin,
is_primary_admin,
require_admin,
not_found,
require_user_api,
)
from devplacepy.utils.text import (
strip_html,
_FLOAT_TOKEN,
plain_float,
_mark_floats,
pretty_json,
slugify,
generate_uid,
make_combined_slug,
time_ago,
extract_mentions,
format_date,
)
from devplacepy.utils.notifications import (
logger,
PUSH_ICON,
DEFAULT_PUSH_URL,
_push_tasks,
_safe_notify,
_schedule_push,
_schedule_telegram,
create_notification,
_deliver_notification,
create_mention_notifications,
_deliver_mention_notifications,
)
from devplacepy.utils.badges import (
LEVEL_BADGES,
BADGE_CATALOG,
BADGE_GROUPS,
get_badge,
build_achievements,
award_badge,
notify_badge,
)
from devplacepy.utils.rewards import (
LEVEL_XP,
XP_POST,
XP_COMMENT,
XP_PROJECT,
XP_GIST,
XP_UPVOTE,
XP_FOLLOW,
XP_QUIZ,
XP_QUIZ_PUBLISH,
XP_QUIZ_COMPLETE,
XP_BATTLE_PART,
XP_BATTLE_WIN,
XP_BATTLE_TOP,
level_for_xp,
award_xp,
_COUNT_MILESTONES,
_milestone_metrics,
check_milestone_badges,
award_rewards,
_apply_rewards,
ACHIEVEMENTS,
UNIQUE_ACTIONS,
track_action,
_apply_achievements,
)
from devplacepy.utils.accounts import (
_create_account,
register_account,
register_account_async,
)