Add optional Matrix thread support for per-category deal organization
When MATRIX_USE_THREADS=true, deals are posted into dedicated threads instead of the room timeline: - 🎮 Game Deals — CheapShark + ITAD type=game - 🧩 DLC Deals — ITAD type=dlc - 🆓 Epic Free Games — current and upcoming Epic free games - 📦 Non-Game Deals — ITAD non-game content (software, courses, etc.) Thread roots are created on first use and persisted in the database. When threads are disabled (default), behaviour is unchanged. Key changes: - New threads.py module with ThreadCategory enum and routing logic - ITADDeal now carries deal_type; type filter moved from fetcher to bot - MatrixDealsClient gains send_deal_in_thread() and create_thread_root() - Database gets thread_roots table for storing root event IDs - Bot routes each deal to the appropriate thread via _send_to_thread_or_room() https://claude.ai/code/session_01EfPjktyrF24DBNHWsY1KBP
This commit is contained in:
59
gaming_deals_bot/threads.py
Normal file
59
gaming_deals_bot/threads.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Thread category definitions for Matrix room threads.
|
||||
|
||||
When ``MATRIX_USE_THREADS=true``, deal messages are posted inside
|
||||
per-category threads rather than directly into the room timeline.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from html import escape
|
||||
|
||||
|
||||
class ThreadCategory(str, Enum):
|
||||
"""Categories that map to distinct Matrix threads."""
|
||||
|
||||
GAME_DEALS = "game_deals"
|
||||
DLC_DEALS = "dlc_deals"
|
||||
EPIC_FREE = "epic_free"
|
||||
NON_GAME_DEALS = "non_game_deals"
|
||||
|
||||
|
||||
# Display metadata for each thread category.
|
||||
# ``label`` is the thread root title; ``description`` appears below it.
|
||||
THREAD_META: dict[ThreadCategory, dict[str, str]] = {
|
||||
ThreadCategory.GAME_DEALS: {
|
||||
"label": "🎮 Game Deals",
|
||||
"description": "PC game deals from CheapShark and IsThereAnyDeal",
|
||||
},
|
||||
ThreadCategory.DLC_DEALS: {
|
||||
"label": "🧩 DLC Deals",
|
||||
"description": "DLC and expansion deals from IsThereAnyDeal",
|
||||
},
|
||||
ThreadCategory.EPIC_FREE: {
|
||||
"label": "🆓 Epic Free Games",
|
||||
"description": "Weekly free games from the Epic Games Store",
|
||||
},
|
||||
ThreadCategory.NON_GAME_DEALS: {
|
||||
"label": "📦 Non-Game Deals",
|
||||
"description": "Software, courses, and other non-game deals",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def itad_type_to_category(deal_type: str) -> ThreadCategory:
|
||||
"""Map an ITAD ``type`` value to a thread category."""
|
||||
if deal_type == "game":
|
||||
return ThreadCategory.GAME_DEALS
|
||||
if deal_type == "dlc":
|
||||
return ThreadCategory.DLC_DEALS
|
||||
return ThreadCategory.NON_GAME_DEALS
|
||||
|
||||
|
||||
def format_thread_root(category: ThreadCategory) -> tuple[str, str]:
|
||||
"""Return ``(plain_text, html)`` for a thread root message."""
|
||||
meta = THREAD_META[category]
|
||||
label = meta["label"]
|
||||
desc = meta["description"]
|
||||
|
||||
plain_text = f"{label}\n{desc}"
|
||||
html = f"<strong>{escape(label)}</strong><br>\n<em>{escape(desc)}</em>"
|
||||
return plain_text, html
|
||||
Reference in New Issue
Block a user