Compare commits
2
Commits
a0dbf3be92
...
7e3b3dff6d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e3b3dff6d | ||
|
|
78f5297383 |
@@ -0,0 +1,2 @@
|
||||
config/*
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,7 @@ password: "zC1b9qCKT8Cv0D"
|
||||
room_id: "!ajyOBbqoCIbBhEqNPU:matrix.org" # Magbot Testing Space
|
||||
#room_id: "!HXiyYVEZOMRtkyvarN:jupiterbroadcasting.com" # Da Lunch
|
||||
#room_id: "!gJYEKNllaubNlNkFIj:jupiterbroadcasting.com" # The Main Chat
|
||||
# Da Lunch and The Main Chat each have their own config (daLunch.yaml, mainChat.yaml).
|
||||
lookback_seconds: 86400 # 24 hours
|
||||
lookback_limit: 400
|
||||
dump_mode: True
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
username: "magbot"
|
||||
password: "zC1b9qCKT8Cv0D"
|
||||
#room_id: "!ajyOBbqoCIbBhEqNPU:matrix.org" # Magbot Testing Space
|
||||
room_id: "!HXiyYVEZOMRtkyvarN:jupiterbroadcasting.com" # Da Lunch
|
||||
#room_id: "!gJYEKNllaubNlNkFIj:jupiterbroadcasting.com" # The Main Chat
|
||||
lookback_seconds: 86400 # 24 hours
|
||||
lookback_limit: 400
|
||||
dump_mode: True
|
||||
dbName: ""
|
||||
# vote_host: "agmninex.online" # Hostname where voting interface is accessible (e.g., "localhost", "example.com", "vote.mysite.xyz")
|
||||
vote_host: "agmninex.online"
|
||||
vote_path: "lunch" # URL slug for this room's voting page (http://<vote_host>:9081/<vote_path>)
|
||||
vote_activity_hours: 6672 # Hours a user must have been active within to be eligible to vote
|
||||
@@ -1,8 +1,8 @@
|
||||
username: "magbot"
|
||||
password: "zC1b9qCKT8Cv0D"
|
||||
room_id: "!ajyOBbqoCIbBhEqNPU:matrix.org" # Magbot Testing Space
|
||||
#room_id: "!ajyOBbqoCIbBhEqNPU:matrix.org" # Magbot Testing Space
|
||||
#room_id: "!HXiyYVEZOMRtkyvarN:jupiterbroadcasting.com" # Da Lunch
|
||||
#room_id: "!gJYEKNllaubNlNkFIj:jupiterbroadcasting.com" # The Main Chat
|
||||
room_id: "!gJYEKNllaubNlNkFIj:jupiterbroadcasting.com" # The Main Chat
|
||||
lookback_seconds: 86400 # 24 hours
|
||||
lookback_limit: 400
|
||||
dump_mode: True
|
||||
|
||||
+5
-3
@@ -9,7 +9,7 @@ import asyncio
|
||||
import sqlite3
|
||||
import time
|
||||
import sys
|
||||
from nio import AsyncClient, LoginResponse
|
||||
from nio import AsyncClient, LoginResponse, RoomMessagesResponse, SyncResponse
|
||||
from room import Room
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ class UserListManager:
|
||||
return []
|
||||
|
||||
# Sync to get room state
|
||||
await self.room.client.sync(timeout=3000)
|
||||
sync_resp = await self.room.client.sync(timeout=3000)
|
||||
if not isinstance(sync_resp, SyncResponse):
|
||||
print(f"Sync failed: {sync_resp}")
|
||||
|
||||
# Get the room object
|
||||
room_obj = self.room.client.rooms.get(self.room.config.room_id)
|
||||
@@ -124,7 +126,7 @@ class UserListManager:
|
||||
limit=batch_size
|
||||
)
|
||||
|
||||
if not response.chunk:
|
||||
if not isinstance(response, RoomMessagesResponse) or not response.chunk:
|
||||
break
|
||||
|
||||
fetched += len(response.chunk)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user