b2541c4054
feat: add configurable matrix permalink base, unify user placeholders, and refactor notification rendering
264 lines
8.1 KiB
Python
Executable File
264 lines
8.1 KiB
Python
Executable File
"""Database utility functions."""
|
|
|
|
import asyncio
|
|
import time
|
|
from typing import List, Dict, Any
|
|
from mautrix.types import PaginationDirection
|
|
|
|
|
|
async def get_messages_to_redact(client, room_id: str, mxid: str, logger) -> List:
|
|
"""Get messages from a user in a room that should be redacted.
|
|
|
|
Args:
|
|
client: Matrix client instance
|
|
room_id: The room ID to search in
|
|
mxid: The user ID whose messages to find
|
|
logger: Logger instance for error reporting
|
|
|
|
Returns:
|
|
list: List of message events to redact
|
|
"""
|
|
try:
|
|
messages = await client.get_messages(
|
|
room_id,
|
|
limit=100,
|
|
filter_json={"senders": [mxid], "not_types": ["m.room.redaction"]},
|
|
direction=PaginationDirection.BACKWARD,
|
|
)
|
|
# Filter out events with empty content
|
|
filtered_events = [
|
|
event
|
|
for event in messages.events
|
|
if event.content and event.content.serialize()
|
|
]
|
|
logger.debug(
|
|
f"DEBUG found {len(filtered_events)} messages to redact in {room_id} (after filtering empty content)"
|
|
)
|
|
return filtered_events
|
|
except Exception as e:
|
|
logger.error(f"Error getting messages to redact: {e}")
|
|
return []
|
|
|
|
|
|
async def redact_messages(
|
|
client, database, room_id: str, sleep_time: float, logger
|
|
) -> Dict[str, int]:
|
|
"""Redact messages queued for redaction in a room.
|
|
|
|
Args:
|
|
client: Matrix client instance
|
|
database: Database instance
|
|
room_id: The room ID to redact messages in
|
|
sleep_time: Sleep time between redactions
|
|
logger: Logger instance for error reporting
|
|
|
|
Returns:
|
|
dict: Counters for successful and failed redactions
|
|
"""
|
|
counters = {"success": 0, "failure": 0}
|
|
events = await database.fetch(
|
|
"SELECT event_id FROM redaction_tasks WHERE room_id = $1", room_id
|
|
)
|
|
for event in events:
|
|
try:
|
|
await client.redact(room_id, event["event_id"], reason="content removed")
|
|
counters["success"] += 1
|
|
await database.execute(
|
|
"DELETE FROM redaction_tasks WHERE event_id = $1", event["event_id"]
|
|
)
|
|
await asyncio.sleep(sleep_time)
|
|
except Exception as e:
|
|
if "Too Many Requests" in str(e):
|
|
logger.warning(
|
|
f"Rate limited while redacting messages in {room_id}, will try again in next loop"
|
|
)
|
|
return counters
|
|
logger.error(f"Failed to redact message: {e}")
|
|
counters["failure"] += 1
|
|
await asyncio.sleep(sleep_time)
|
|
return counters
|
|
|
|
|
|
async def upsert_user_timestamp(database, mxid: str, timestamp: int, logger) -> None:
|
|
"""Insert or update user activity timestamp.
|
|
|
|
Args:
|
|
database: Database instance
|
|
mxid: User Matrix ID
|
|
timestamp: Activity timestamp
|
|
logger: Logger instance for error reporting
|
|
"""
|
|
try:
|
|
await database.execute(
|
|
"""
|
|
INSERT INTO user_events (mxid, last_message_timestamp, ignore_inactivity)
|
|
VALUES ($1, $2, 0)
|
|
ON CONFLICT (mxid) DO UPDATE SET
|
|
last_message_timestamp = EXCLUDED.last_message_timestamp
|
|
""",
|
|
mxid,
|
|
timestamp,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to upsert user timestamp: {e}")
|
|
|
|
|
|
async def get_inactive_users(
|
|
database, warn_threshold_days: int, kick_threshold_days: int, logger
|
|
) -> Dict[str, List[str]]:
|
|
"""Get lists of users who should be warned or kicked for inactivity.
|
|
|
|
Args:
|
|
database: Database instance
|
|
warn_threshold_days: Days threshold for warning
|
|
kick_threshold_days: Days threshold for kicking
|
|
logger: Logger instance for error reporting
|
|
|
|
Returns:
|
|
dict: Contains 'warn' and 'kick' lists of user IDs
|
|
"""
|
|
try:
|
|
current_time = int(time.time())
|
|
warn_threshold = current_time - (warn_threshold_days * 24 * 60 * 60)
|
|
kick_threshold = current_time - (kick_threshold_days * 24 * 60 * 60)
|
|
|
|
# Get users to warn
|
|
warn_results = await database.fetch(
|
|
"""
|
|
SELECT mxid FROM user_events
|
|
WHERE last_message_timestamp < $1
|
|
AND last_message_timestamp > $2
|
|
AND ignore_inactivity = 0
|
|
""",
|
|
warn_threshold,
|
|
kick_threshold,
|
|
)
|
|
|
|
# Get users to kick
|
|
kick_results = await database.fetch(
|
|
"""
|
|
SELECT mxid FROM user_events
|
|
WHERE last_message_timestamp < $2
|
|
AND ignore_inactivity = 0
|
|
""",
|
|
kick_threshold,
|
|
)
|
|
|
|
return {
|
|
"warn": [row["mxid"] for row in warn_results],
|
|
"kick": [row["mxid"] for row in kick_results],
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Failed to get inactive users: {e}")
|
|
return {"warn": [], "kick": []}
|
|
|
|
|
|
async def cleanup_stale_verification_states(database, logger) -> None:
|
|
"""Clean up stale verification states older than 24 hours.
|
|
|
|
Args:
|
|
database: Database instance
|
|
logger: Logger instance for error reporting
|
|
"""
|
|
try:
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM verification_states
|
|
WHERE created_at < NOW() - INTERVAL '24 hours'
|
|
"""
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to cleanup stale verification states: {e}")
|
|
|
|
|
|
async def get_verification_state(database, dm_room_id: str) -> Dict[str, Any]:
|
|
"""Get verification state for a DM room.
|
|
|
|
Args:
|
|
database: Database instance
|
|
dm_room_id: The DM room ID
|
|
|
|
Returns:
|
|
dict: Verification state data or None if not found
|
|
"""
|
|
try:
|
|
result = await database.fetchrow(
|
|
"SELECT * FROM verification_states WHERE dm_room_id = $1", dm_room_id
|
|
)
|
|
return dict(result) if result else None
|
|
except Exception as e:
|
|
return None
|
|
|
|
|
|
async def create_verification_state(
|
|
database,
|
|
dm_room_id: str,
|
|
user_id: str,
|
|
target_room_id: str,
|
|
verification_phrase: str,
|
|
attempts_remaining: int,
|
|
required_power_level: int,
|
|
) -> None:
|
|
"""Create a new verification state.
|
|
|
|
Args:
|
|
database: Database instance
|
|
dm_room_id: The DM room ID
|
|
user_id: The user ID being verified
|
|
target_room_id: The target room ID
|
|
verification_phrase: The phrase to verify
|
|
attempts_remaining: Number of attempts remaining
|
|
required_power_level: Required power level for the target room
|
|
"""
|
|
try:
|
|
await database.execute(
|
|
"""
|
|
INSERT INTO verification_states
|
|
(dm_room_id, user_id, target_room_id, verification_phrase, attempts_remaining, required_power_level)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
""",
|
|
dm_room_id,
|
|
user_id,
|
|
target_room_id,
|
|
verification_phrase,
|
|
attempts_remaining,
|
|
required_power_level,
|
|
)
|
|
except Exception as e:
|
|
pass # Verification state creation is not critical
|
|
|
|
|
|
async def update_verification_attempts(
|
|
database, dm_room_id: str, attempts_remaining: int
|
|
) -> None:
|
|
"""Update verification attempts remaining.
|
|
|
|
Args:
|
|
database: Database instance
|
|
dm_room_id: The DM room ID
|
|
attempts_remaining: New number of attempts remaining
|
|
"""
|
|
try:
|
|
await database.execute(
|
|
"UPDATE verification_states SET attempts_remaining = $1 WHERE dm_room_id = $2",
|
|
attempts_remaining,
|
|
dm_room_id,
|
|
)
|
|
except Exception as e:
|
|
pass # Verification state update is not critical
|
|
|
|
|
|
async def delete_verification_state(database, dm_room_id: str) -> None:
|
|
"""Delete a verification state.
|
|
|
|
Args:
|
|
database: Database instance
|
|
dm_room_id: The DM room ID
|
|
"""
|
|
try:
|
|
await database.execute(
|
|
"DELETE FROM verification_states WHERE dm_room_id = $1", dm_room_id
|
|
)
|
|
except Exception as e:
|
|
pass # Verification state deletion is not critical
|