Pin co-op invite posts; unpin on lock/cancel

Adds Base.PinEvent / Base.UnpinEvent helpers (m.room.pinned_events state)
that read-modify-write the existing pin list — idempotent on both sides.
The bot needs power level for state events; failures are logged but not
fatal.

Schema: add coop_dungeon_runs.invite_post_id to remember which event to
unpin. Migration entry included.

Wired:
- !coop start: pin the invite post in the games room
- !coop cancel: unpin before posting cancellation
- coopProcessLocks (auto-lock or auto-cancel): unpin before lock/expiry post

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-26 10:24:21 -07:00
parent 91af4f1e83
commit f3d1e65bf1
5 changed files with 86 additions and 3 deletions

View File

@@ -24,6 +24,7 @@ type CoopRun struct {
GoldPool int
RewardTotal int
LastResolvedDay int
InvitePostID id.EventID
CreatedAt time.Time
LockedAt *time.Time
CompletedAt *time.Time
@@ -55,10 +56,11 @@ func loadCoopRun(runID int) (*CoopRun, error) {
var leader string
var lockedAt, completedAt sql.NullTime
err := d.QueryRow(`SELECT id, tier, status, leader_id, current_day, total_days,
base_difficulty, gold_pool, reward_total, last_resolved_day, created_at, locked_at, completed_at
base_difficulty, gold_pool, reward_total, last_resolved_day, invite_post_id, created_at, locked_at, completed_at
FROM coop_dungeon_runs WHERE id = ?`, runID).Scan(
&r.ID, &r.Tier, &r.Status, &leader, &r.CurrentDay, &r.TotalDays,
&r.BaseDifficulty, &r.GoldPool, &r.RewardTotal, &r.LastResolvedDay,
(*string)(&r.InvitePostID),
&r.CreatedAt, &lockedAt, &completedAt)
if err != nil {
return nil, err
@@ -94,7 +96,7 @@ func loadMostRecentOpenCoopRun() (*CoopRun, error) {
func queryCoopRuns(whereClause string) ([]*CoopRun, error) {
d := db.Get()
rows, err := d.Query(`SELECT id, tier, status, leader_id, current_day, total_days,
base_difficulty, gold_pool, reward_total, last_resolved_day, created_at, locked_at, completed_at
base_difficulty, gold_pool, reward_total, last_resolved_day, invite_post_id, created_at, locked_at, completed_at
FROM coop_dungeon_runs WHERE ` + whereClause)
if err != nil {
return nil, err
@@ -108,6 +110,7 @@ func queryCoopRuns(whereClause string) ([]*CoopRun, error) {
var lockedAt, completedAt sql.NullTime
if err := rows.Scan(&r.ID, &r.Tier, &r.Status, &leader, &r.CurrentDay, &r.TotalDays,
&r.BaseDifficulty, &r.GoldPool, &r.RewardTotal, &r.LastResolvedDay,
(*string)(&r.InvitePostID),
&r.CreatedAt, &lockedAt, &completedAt); err != nil {
return nil, err
}
@@ -169,6 +172,12 @@ func completeCoopRun(runID int, status string, reward int) error {
return err
}
func setCoopRunInvitePostID(runID int, postID id.EventID) error {
d := db.Get()
_, err := d.Exec(`UPDATE coop_dungeon_runs SET invite_post_id = ? WHERE id = ?`, string(postID), runID)
return err
}
// setCoopRunLastResolvedDay marks a day as resolved for crash-resume idempotency.
// Resolution is idempotent: re-entering coopResolveFloor for the same day is a no-op.
func setCoopRunLastResolvedDay(runID, day int) error {