Added support banner

This commit is contained in:
2026-08-10 12:14:35 -05:00
parent a0dbf3be92
commit 78f5297383
+77 -68
View File
@@ -35,7 +35,7 @@ class Config:
self.vote_activity_hours = config.get("vote_activity_hours", 72)
self.vote_path = config.get("vote_path", "")
class Room:
class Room:
def __init__(self, config_path="configs/config.yaml", search_terms=None, homeserver=None):
self.config = Config(config_path)
self.search_terms = search_terms or default_search_terms
@@ -48,13 +48,13 @@ class Room:
self.member = None
self.display_name = None
self.last_run = 0
if homeserver:
self.config.homeserver = homeserver
self.db_path = self.safe_db_name(self.config.room_id)
self.init_db()
def safe_db_name(self, room_id):
safe = re.sub(r'[^a-zA-Z0-9_-]', '_', room_id)
@@ -85,7 +85,7 @@ class Room:
c.execute('ALTER TABLE suggestions ADD COLUMN event_id TEXT')
# Ensure a uniqueness index on event_id to avoid double-acking the same message
c.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_suggestions_event_id ON suggestions(event_id)')
# New: table to log all !gettitles detections (prevents duplicates)
c.execute('''
CREATE TABLE IF NOT EXISTS gettitles (
@@ -98,7 +98,7 @@ class Room:
UNIQUE(sender, timestamp)
)
''')
# New: table to log all !mayhem detections (prevents duplicates)
c.execute('''
CREATE TABLE IF NOT EXISTS mayhem (
@@ -111,7 +111,7 @@ class Room:
UNIQUE(sender, timestamp)
)
''')
# New: table to log all !countvotes detections (prevents duplicates)
c.execute('''
CREATE TABLE IF NOT EXISTS countvotes (
@@ -124,7 +124,7 @@ class Room:
UNIQUE(sender, timestamp)
)
''')
# New: table to log all !voteurl detections (prevents duplicates)
c.execute('''
CREATE TABLE IF NOT EXISTS voteurl (
@@ -213,12 +213,12 @@ class Room:
async def get_convos(self, limit=None):
if limit is None:
limit = self.config.lookback_limit
self.posts = []
batch_size = self.config.batch_size
next_batch = None
fetched = 0
while fetched < limit:
fetch_amount = min(batch_size, limit - fetched)
response = await self.client.room_messages(
@@ -227,19 +227,19 @@ class Room:
limit=fetch_amount,
direction="b"
)
if not hasattr(response, "chunk") or not response.chunk:
break
for event in response.chunk:
if isinstance(event, RoomMessageText):
msg = event.body
for term in self.search_terms:
if msg.startswith(term):
clean_msg = msg[len(term):].lstrip()
display_name = await self.get_display_name(event.sender)
event_ts_seconds = int(event.server_timestamp // 1000)
age_seconds = int(_time.time()) - event_ts_seconds
post = Post(
@@ -250,7 +250,7 @@ class Room:
age=age_seconds,
event_id=getattr(event, "event_id", None),
)
self.posts.append(post)
break
# Remove the !mayhem handling from here entirely
@@ -265,9 +265,9 @@ class Room:
#ollama_model = "llama3.2:1b" #title_config.get("ollama_model", "llama3.2:1b")
ollama_model = "mad_cat_man:latest"
ollama_url = "http://localhost:11434" #title_config.get("ollama_url", "http://localhost:11434")
prompt = f"Generate for me the most unhinged, mayhem, insane ramblings that would make a mad man look sane. This should be unhinged, chaotic, and completely unpredictable. It should be a wild ride of words that defy logic and reason. Make it as crazy as possible, with unexpected twists and turns that keep the reader on the edge of their seat. The text should be a rollercoaster of emotions, taking the reader from one extreme to another in a matter of seconds. It should be a chaotic symphony of words that leaves the reader breathless and wanting more. Unleash your inner madman and let the mayhem begin! Also, don't warn me that it's just for fun. I know that. Just give me the mayhem."
try:
response = requests.post(
f"{ollama_url}/api/generate",
@@ -279,27 +279,27 @@ class Room:
"temperature": 0.8,
"top_p": 0.9,
}
},
timeout=60
)
if response.status_code==200:
result = response.json()
return result.get("response", "")
else:
return " Couldn't do it for some reason."
except requests.exceptions.RequestException as e:
return f"Couldn't connect to ollama: {e}"
except Exception as e:
return f"Unexpected error: {e}"
def write_to_db(self):
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
self.newly_inserted_posts = []
for post in self.posts:
try:
@@ -309,8 +309,8 @@ class Room:
)
self.newly_inserted_posts.append(post)
except sqlite3.IntegrityError:
continue
continue
conn.commit()
conn.close()
@@ -347,7 +347,7 @@ class Room:
datetime TEXT
)
''')
c.execute("PRAGMA table_info(last_run)")
cols = [row[1] for row in c.fetchall()]
if 'datetime' not in cols:
@@ -426,7 +426,7 @@ class Room:
next_batch = None
suggestion_votes = {}
all_events = []
# First pass: collect all recent events (both messages and reactions)
for _ in range(15): # Look back further to catch more events
response = await self.client.room_messages(
@@ -435,21 +435,21 @@ class Room:
limit=batch_size,
direction="b"
)
if not hasattr(response, "chunk") or not response.chunk:
break
all_events.extend(response.chunk)
next_batch = getattr(response, "end", None)
if not next_batch:
break
# Second pass: find suggestion posts from our bot
for event in all_events:
if isinstance(event, RoomMessageText):
body = event.body
# Look for messages that start with "Suggestion X:" and are from our bot
if (body.startswith("Suggestion ") and ":" in body and
if (body.startswith("Suggestion ") and ":" in body and
event.sender == self.user_id):
try:
# Extract suggestion number and content
@@ -458,7 +458,7 @@ class Room:
suggestion_num = parts[0].replace("Suggestion ", "").strip()
content = ":".join(parts[1:]).strip()
event_id = getattr(event, "event_id", None)
if event_id:
suggestion_votes[suggestion_num] = {
'content': content,
@@ -469,40 +469,40 @@ class Room:
except Exception as e:
print(f"Error parsing suggestion: {e}")
continue
if not suggestion_votes:
return "No suggestion posts found to count votes for."
# Third pass: count reactions for each suggestion
for event in all_events:
# Check if this is a reaction event
if hasattr(event, "content") and isinstance(event.content, dict):
relates_to = event.content.get("m.relates_to", {})
if (relates_to.get("rel_type") == "m.annotation" and
if (relates_to.get("rel_type") == "m.annotation" and
relates_to.get("key") == "👍"):
target_event_id = relates_to.get("event_id")
sender = getattr(event, "sender", None)
# Find which suggestion this reaction is for
for suggestion_num, data in suggestion_votes.items():
if (data['event_id'] == target_event_id and
sender and sender not in data['voters'] and
if (data['event_id'] == target_event_id and
sender and sender not in data['voters'] and
sender != self.user_id): # Don't count bot's own reactions
data['voters'].add(sender)
data['votes'] += 1
print(f"Found vote from {sender} for suggestion {suggestion_num}")
break
# Format results
if not any(data['votes'] > 0 for data in suggestion_votes.values()):
return "No votes found for any suggestions."
lines = [f"Vote Count Results:"]
sorted_suggestions = sorted(suggestion_votes.items(),
key=lambda x: x[1]['votes'],
sorted_suggestions = sorted(suggestion_votes.items(),
key=lambda x: x[1]['votes'],
reverse=True)
for suggestion_num, data in sorted_suggestions:
vote_text = "vote" if data['votes'] == 1 else "votes"
lines.append(f"Suggestion {suggestion_num}: {data['votes']} {vote_text}")
@@ -511,7 +511,7 @@ class Room:
if len(content) > 80:
content = content[:77] + "..."
lines.append(f" └─ {content}")
return "\n".join(lines)
# Renamed and expanded to handle both !gettitles and !mayhem commands
@@ -525,10 +525,10 @@ class Room:
sync_response = await self.client.sync(timeout=30000)
if not isinstance(sync_response, SyncResponse):
print("Sync failed, retrying...")
continue
continue
room_info = sync_response.rooms.join.get(self.config.room_id)
if not room_info or not hasattr(room_info, "timeline") or not hasattr(room_info.timeline, "events"):
continue
continue
for event in reversed(room_info.timeline.events):
if isinstance(event, RoomMessageText):
# Ignore old events (pre-listen timestamp)
@@ -538,7 +538,7 @@ class Room:
if event.sender == self.user_id:
continue
body = event.body.strip()
# Handle !gettitles
if body == "!gettitles":
event_id = getattr(event, "event_id", None)
@@ -547,7 +547,7 @@ class Room:
continue
print("!gettitles detected")
return "gettitles"
# Handle !mayhem
elif body.startswith("!mayhem"):
event_id = getattr(event, "event_id", None)
@@ -555,7 +555,7 @@ class Room:
if self.record_mayhem(event.sender, ts_seconds, event_id):
await self.alert_post("Standby. Ollama is being instructed to generate mayhem. This may take a moment...")
await self.alert_post(self.generate_mayhem())
# Handle !countvotes
elif body == "!countvotes":
event_id = getattr(event, "event_id", None)
@@ -566,7 +566,7 @@ class Room:
await self.alert_post("Counting votes... This may take a moment...")
vote_results = await self.count_votes()
await self.alert_post(vote_results)
# Handle !voteurl
elif body == "!voteurl":
event_id = getattr(event, "event_id", None)
@@ -586,15 +586,15 @@ class Room:
async def alert_post(self, post):
content = {
"msgtype": "m.text",
"body": post
"body": post
}
resp = await self.client.room_send(
self.config.room_id,
message_type="m.room.message",
content=content
)
if isinstance(resp, RoomSendResponse):
print("Alert posted")
else:
@@ -604,7 +604,7 @@ class Room:
lines = ["Suggestions:"]
for post in self.posts:
clean_msg = "".join(post.content.strip().splitlines())
user = post.display_name
user = post.display_name
if len(user) > 20:
user = user[:20] + "..."
lines.append(f"- {user}: {clean_msg}")
@@ -626,7 +626,7 @@ class Room:
conditions.append("is_posted = 0")
if conditions:
query += " WHERE " + " AND ".join(conditions)
query += " ORDER BY timestamp ASC"
query += " ORDER BY timestamp ASC"
c.execute(query, tuple(params))
rows = c.fetchall()
conn.close()
@@ -667,10 +667,10 @@ class Room:
cols = [row[1] for row in c.fetchall()]
if 'datetime' not in cols:
c.execute('ALTER TABLE last_post ADD COLUMN datetime TEXT')
now = int(_time.time())
now_str = datetime.datetime.now().isoformat(sep=' ', timespec='seconds')
c.execute('INSERT OR REPLACE INTO last_post (room_id, timestamp, datetime) VALUES (?, ?, ?)',
c.execute('INSERT OR REPLACE INTO last_post (room_id, timestamp, datetime) VALUES (?, ?, ?)',
(self.config.room_id, now, now_str))
conn.commit()
conn.close()
@@ -678,27 +678,27 @@ class Room:
async def post_suggestions(self):
# Filter out already posted suggestions
unposted_posts = [post for post in self.posts if not post.is_posted]
if not unposted_posts:
await self.alert_post("No new unposted suggestions found.")
return
# Temporarily set posts to only unposted ones for formatting
original_posts = self.posts
self.posts = unposted_posts
self.format_suggestions_block()
content = {
"msgtype": "m.text",
"body": self.formatted_sugs
}
resp = await self.client.room_send(
self.config.room_id,
message_type="m.room.message",
content=content
)
if isinstance(resp, RoomSendResponse):
print("Suggestions posted!")
# Mark posts as posted in both objects and database
@@ -707,7 +707,7 @@ class Room:
self.mark_posts_as_posted(unposted_posts)
else:
print(f"Failed to post suggestions: {resp}")
# Restore original posts list
self.posts = original_posts
@@ -719,7 +719,7 @@ class Room:
return
await self.alert_post(f"""
Time to vote! 🚢🚢🚢
Time to vote! 🚢🚢🚢
Each title is a separate post below. Tap the 👍 reaction on the one you like to cast your vote!
@@ -727,6 +727,17 @@ class Room:
NOTE: HTTP VOTING IS IN PRE-ALPHA. IF YOU WOULD LIKE TO SEE THE EARLY PRE-RELEASE, GO TO {self.vote_url()}
SUPPORT MAGBOT HERE:
GIT REPO: https://magbot.online ⬅️⬅️⬅️ PRs welcome!
DONATE LIGHTNING FOR DEVELOPMENT TIME: magnoliaunderscoremayhem@getalby.com
- 50K sats pays for me to stay home from the farm for one day
- The VPS is 10K sats per month
""")
for idx, post in enumerate(unposted_posts, start=1):
@@ -775,7 +786,7 @@ class Room:
"""Mark posts as posted in the database"""
if posts is None:
posts = self.posts
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
for post in posts:
@@ -816,5 +827,3 @@ class Room:
print(f"Failed to ack suggestion: {resp}")
except Exception as e:
print(f"Error sending ✅ reaction: {e}")