Matrix-Bot-Jean
#!/bin/bash
set -e
cd /root
cat > create_jean_complete_fixed.sh << 'ENDOFSCRIPT'
#!/bin/bash
set -e
PROJECT="jean-matrix-bot-complete"
echo "๐ Creating Complete Jean Matrix Bot with all fixes..."
rm -rf "$PROJECT"
mkdir -p "$PROJECT"
cd "$PROJECT"
# ============================================
# Verzeichnisstruktur
# ============================================
mkdir -p jean/{core,plugins/{builtin,custom},utils}
mkdir -p config data logs
# ============================================
# 1. TOKEN GENERATOR (VERBESSERT)
# ============================================
cat > get_token.py << 'PYEOF'
#!/usr/bin/env python3
"""
Matrix Access Token Generator - Improved Version
Generiert einen Access Token fรผr den Jean Bot
"""
import asyncio
import sys
from nio import AsyncClient, LoginResponse, LoginError
async def get_token(homeserver: str, username: str, password: str, device_name: str = "Jean Bot"):
"""Generate access token"""
# Ensure @ prefix
if not username.startswith("@"):
username = f"@{username}"
# Remove trailing slash from homeserver
homeserver = homeserver.rstrip('/')
print(f"\n๐ Attempting login...")
print(f"ย Homeserver: {homeserver}")
print(f"ย Username: {username}")
print(f"ย Device: {device_name}")
print()
client = AsyncClient(homeserver, username)
try:
# Attempt login
response = await client.login(password, device_name=device_name)
if isinstance(response, LoginResponse):
print("โ
Login successful!")
print()
print("="*70)
print("๐ YOUR ACCESS TOKEN:")
print("="*70)
print()
print(f"MATRIX_ACCESS_TOKEN={response.access_token}")
print(f"MATRIX_DEVICE_ID={response.device_id}")
print(f"MATRIX_USER={username}")
print(f"MATRIX_HOMESERVER={homeserver}")
print()
print("="*70)
print()
# Try to update .env file
try:
import os
if os.path.exists('.env'):
with open('.env', 'r') as f:
lines = f.readlines()
# Update or add lines
updated_lines = []
keys_updated = set()
for line in lines:
if line.startswith('MATRIX_ACCESS_TOKEN='):
updated_lines.append(f'MATRIX_ACCESS_TOKEN={response.access_token}\n')
keys_updated.add('token')
elif line.startswith('MATRIX_DEVICE_ID='):
updated_lines.append(f'MATRIX_DEVICE_ID={response.device_id}\n')
keys_updated.add('device')
elif line.startswith('MATRIX_USER='):
updated_lines.append(f'MATRIX_USER={username}\n')
keys_updated.add('user')
elif line.startswith('MATRIX_HOMESERVER='):
updated_lines.append(f'MATRIX_HOMESERVER={homeserver}\n')
keys_updated.add('homeserver')
else:
updated_lines.append(line)
# Add missing keys
if 'token' not in keys_updated:
updated_lines.append(f'MATRIX_ACCESS_TOKEN={response.access_token}\n')
if 'device' not in keys_updated:
updated_lines.append(f'MATRIX_DEVICE_ID={response.device_id}\n')
if 'user' not in keys_updated:
updated_lines.append(f'MATRIX_USER={username}\n')
if 'homeserver' not in keys_updated:
updated_lines.append(f'MATRIX_HOMESERVER={homeserver}\n')
# Write back
with open('.env', 'w') as f:
f.writelines(updated_lines)
print("โ
.env file updated automatically!")
print()
else:
print("โน๏ธย No .env file found.")
print("ย Create one with: cp .env.example .env")
print()
except Exception as e:
print(f"โ ๏ธย Could not update .env: {e}")
print("ย Please copy the values above manually.")
print()
return True
elif isinstance(response, LoginError):
print("โ Login failed!")
print(f"ย Error: {response.message}")
print(f"ย Status: {response.status_code}")
print()
if response.status_code == 403:
print("๐ก Possible reasons:")
print("ย - Wrong password")
print("ย - Account locked")
print("ย - User doesn't exist")
elif response.status_code == 429:
print("๐ก Too many login attempts. Wait a moment and try again.")
else:
print(f"๐ก HTTP Status: {response.status_code}")
return False
else:
print(f"โ Unexpected response: {type(response)}")
print(f"ย {response}")
return False
except Exception as e:
print(f"โ Error during login: {e}")
print()
import traceback
traceback.print_exc()
return False
finally:
await client.close()
def main():
print()
print("="*70)
print("๐ค Jean Matrix Bot - Token Generator")
print("="*70)
print()
# Check for command line arguments
if len(sys.argv) >= 4:
homeserver = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
device_name = sys.argv[4] if len(sys.argv) > 4 else "Jean Bot"
print("๐ Using command line arguments")
else:
# Interactive mode
print("๐ Interactive Mode")
print()
homeserver = input("Homeserver URL (e.g. https://matrix.org): ").strip()
if not homeserver:
print("โ Homeserver is required!")
sys.exit(1)
username = input("Username (e.g. @jean:matrix.org or jean): ").strip()
if not username:
print("โ Username is required!")
sys.exit(1)
password = input("Password: ").strip()
if not password:
print("โ Password is required!")
sys.exit(1)
device_name = input("Device Name [Jean Bot]: ").strip() or "Jean Bot"
# Run async function
success = asyncio.run(get_token(homeserver, username, password, device_name))
if success:
print("๐ Next steps:")
print("ย 1. Run: python -m jean.core.bot")
print("ย 2. Invite the bot to a room")
print("ย 3. Send: !help")
print()
sys.exit(0)
else:
print("โ Token generation failed.")
print()
print("๐ก Troubleshooting:")
print("ย 1. Check homeserver URL (must start with https://)")
print("ย 2. Verify username format (@user:server.com)")
print("ย 3. Confirm password is correct")
print("ย 4. Check if user exists on the homeserver")
print()
sys.exit(1)
if __name__ == "__main__":
main()
PYEOF
chmod +x get_token.py
# ============================================
# 2. CONFIGURATION
# ============================================
cat > jean/core/config.py << 'EOF'
"""
Configuration Manager fรผr Jean Bot
"""
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
class Config:
"""Bot Configuration"""
def __init__(self):
# Matrix Config
self.homeserver = os.getenv('MATRIX_HOMESERVER')
self.user = os.getenv('MATRIX_USER')
self.access_token = os.getenv('MATRIX_ACCESS_TOKEN')
self.device_id = os.getenv('MATRIX_DEVICE_ID', 'JEAN_BOT')
self.password = os.getenv('MATRIX_PASSWORD')
# Ollama Config
self.ollama_url = os.getenv('OLLAMA_URL', 'http://localhost:11434')
self.model = os.getenv('OLLAMA_MODEL', 'llama3.2:latest')
self.vision_model = os.getenv('OLLAMA_VISION_MODEL', 'llava:latest')
# Bot Settings
self.command_prefix = os.getenv('COMMAND_PREFIX', '!')
self.log_level = os.getenv('LOG_LEVEL', 'INFO')
# Database
self.database_url = os.getenv('DATABASE_URL', 'sqlite:///data/jean.db')
# Admin Users
admin_str = os.getenv('ADMIN_USERS', '')
self.admin_users = [u.strip() for u in admin_str.split(',') if u.strip()]
# Feature Flags
self.enable_image_analysis = os.getenv('ENABLE_IMAGE_ANALYSIS', 'true').lower() == 'true'
self.enable_voice_transcription = os.getenv('ENABLE_VOICE_TRANSCRIPTION', 'false').lower() == 'true'
self.enable_document_analysis = os.getenv('ENABLE_DOCUMENT_ANALYSIS', 'true').lower() == 'true'
# Validate
self._validate()
def _validate(self):
"""Validate required config"""
required = {
'homeserver': self.homeserver,
'user': self.user,
}
if not self.access_token and not self.password:
required['access_token or password'] = None
missing = [k for k, v in required.items() if not v]
if missing:
raise ValueError(f"Missing required config: {', '.join(missing)}")
EOF
# ============================================
# 3. OLLAMA CLIENT
# ============================================
cat > jean/utils/ollama_client.py << 'EOF'
"""
Ollama Client fรผr Jean Bot
"""
import aiohttp
import logging
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
class OllamaClient:
"""Client fรผr Ollama API"""
def __init__(self, base_url: str, model: str, vision_model: str = None):
self.base_url = base_url.rstrip('/')
self.model = model
self.vision_model = vision_model or model
async def generate(
self,ย
prompt: str,ย
model: Optional[str] = None,
images: Optional[List[str]] = None,
system: Optional[str] = None
) -> Dict:
"""Generate completion"""
try:
url = f"{self.base_url}/api/generate"
payload = {
"model": model or self.model,
"prompt": prompt,
"stream": False
}
if images:
payload["images"] = images
if system:
payload["system"] = system
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
if resp.status != 200:
error_text = await resp.text()
raise Exception(f"Ollama API error {resp.status}: {error_text}")
return await resp.json()
except Exception as e:
logger.error(f"Ollama generate error: {e}")
raise
async def chat(
self,
messages: List[Dict[str, str]],
model: Optional[str] = None,
system: Optional[str] = None
) -> Dict:
"""Chat completion"""
try:
url = f"{self.base_url}/api/chat"
payload = {
"model": model or self.model,
"messages": messages,
"stream": False
}
if system:
payload["messages"].insert(0, {"role": "system", "content": system})
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
if resp.status != 200:
error_text = await resp.text()
raise Exception(f"Ollama API error {resp.status}: {error_text}")
return await resp.json()
except Exception as e:
logger.error(f"Ollama chat error: {e}")
raise
async def list_models(self) -> List[str]:
"""List available models"""
try:
url = f"{self.base_url}/api/tags"
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
return []
data = await resp.json()
return [m['name'] for m in data.get('models', [])]
except Exception as e:
logger.error(f"List models error: {e}")
return []
EOF
# ============================================
# 4. MEDIA HANDLER (FIXED)
# ============================================
cat > jean/core/media_handler.py << 'EOF'
"""
Fixed Media Handler fรผr Jean Matrix Bot
Behebt Download-Probleme fรผr Bilder, Audio, PDFs und DOCX
"""
import asyncio
import aiohttp
import aiofiles
import logging
from pathlib import Path
from typing import Optional, Tuple
from nio import AsyncClient, DownloadError, DownloadResponse
import mimetypes
logger = logging.getLogger(__name__)
class MediaHandler:
"""Handhabt Media-Downloads mit verbessertem Error-Handling"""
def __init__(self, client: AsyncClient, temp_dir: str = "/tmp/jean_media"):
self.client = client
self.temp_dir = Path(temp_dir)
self.temp_dir.mkdir(parents=True, exist_ok=True)
async def download_media(
self,ย
mxc_url: str,ย
filename: Optional[str] = None
) -> Optional[Tuple[Path, str]]:
"""Download media from mxc:// URL"""
if not mxc_url or not mxc_url.startswith("mxc://"):
logger.error(f"Ungรผltige MXC URL: {mxc_url}")
return None
try:
logger.info(f"๐ฅ Downloading: {mxc_url}")
parts = mxc_url.replace("mxc://", "").split("/")
if len(parts) < 2:
logger.error(f"Fehlerhafte MXC URL Struktur: {mxc_url}")
return None
server_name = parts[0]
media_id = "/".join(parts[1:])
# Method 1: nio download
response = await self.client.download(
server_name=server_name,
media_id=media_id
)
if isinstance(response, DownloadError):
logger.warning(f"nio download fehlgeschlagen: {response.message}")
return await self._download_via_http(mxc_url, filename)
if not isinstance(response, DownloadResponse):
logger.error(f"Unerwartete Response: {type(response)}")
return await self._download_via_http(mxc_url, filename)
if not filename:
filename = f"media_{media_id.replace('/', '_')}"
mime_type = response.content_type or "application/octet-stream"
ext = mimetypes.guess_extension(mime_type)
if ext and not filename.endswith(ext):
filename = f"{filename}{ext}"
filepath = self.temp_dir / filename
async with aiofiles.open(filepath, 'wb') as f:
await f.write(response.body)
file_size = len(response.body) / 1024
logger.info(f"โ
Download erfolgreich: {filename} ({file_size:.2f} KB)")
return filepath, mime_type
except Exception as e:
logger.error(f"โ Download fehlgeschlagen fรผr {mxc_url}: {e}")
return await self._download_via_http(mxc_url, filename)
async def _download_via_http(
self,ย
mxc_url: str,ย
filename: Optional[str] = None
) -> Optional[Tuple[Path, str]]:
"""Fallback: HTTP Download"""
try:
parts = mxc_url.replace("mxc://", "").split("/", 1)
if len(parts) < 2:
return None
server_name, media_id = parts
homeserver = self.client.homeserver
download_url = f"{homeserver}/_matrix/media/v3/download/{server_name}/{media_id}"
logger.info(f"๐ Fallback HTTP Download: {download_url}")
async with aiohttp.ClientSession() as session:
headers = {
"Authorization": f"Bearer {self.client.access_token}"
}
async with session.get(download_url, headers=headers) as resp:
if resp.status != 200:
logger.error(f"HTTP {resp.status}: {await resp.text()}")
return None
content = await resp.read()
mime_type = resp.headers.get('Content-Type', 'application/octet-stream')
if not filename:
content_disp = resp.headers.get('Content-Disposition', '')
if 'filename=' in content_disp:
filename = content_disp.split('filename=')[1].strip('"')
else:
filename = f"media_{media_id.replace('/', '_')}"
ext = mimetypes.guess_extension(mime_type)
if ext and not filename.endswith(ext):
filename = f"{filename}{ext}"
filepath = self.temp_dir / filename
async with aiofiles.open(filepath, 'wb') as f:
await f.write(content)
file_size = len(content) / 1024
logger.info(f"โ
HTTP Download erfolgreich: {filename} ({file_size:.2f} KB)")
return filepath, mime_type
except Exception as e:
logger.error(f"โ HTTP Download fehlgeschlagen: {e}")
return None
async def cleanup_old_files(self, max_age_hours: int = 24):
"""Lรถsche alte temporรคre Dateien"""
import time
cutoff = time.time() - (max_age_hours * 3600)
for filepath in self.temp_dir.iterdir():
if filepath.is_file() and filepath.stat().st_mtime < cutoff:
try:
filepath.unlink()
logger.debug(f"๐๏ธ Gelรถscht: {filepath.name}")
except Exception as e:
logger.error(f"Fehler beim Lรถschen {filepath}: {e}")
class MediaEventHandler:
"""Event Handler fรผr Media-Events"""
def __init__(self, bot):
self.bot = bot
self.media_handler = MediaHandler(bot.client)
async def handle_image(self, room, event):
"""Handler fรผr Bild-Events"""
try:
logger.info(f"๐ผ๏ธ Bild empfangen in {room.display_name}")
result = await self.media_handler.download_media(event.url, event.body)
if not result:
await self.bot.send_message(room.room_id, "โ Fehler beim Herunterladen des Bildes")
return
filepath, mime_type = result
logger.info(f"โ
Bild gespeichert: {filepath}")
analysis = await self._analyze_image(filepath)
await self.bot.send_message(room.room_id, f"๐ Bildanalyse:\n\n{analysis}")
except Exception as e:
logger.error(f"Fehler bei Bildverarbeitung: {e}")
await self.bot.send_message(room.room_id, f"โ Fehler: {str(e)}")
async def handle_audio(self, room, event):
"""Handler fรผr Audio-Events"""
try:
logger.info(f"๐ต Audio empfangen in {room.display_name}")
result = await self.media_handler.download_media(event.url, event.body)
if not result:
await self.bot.send_message(room.room_id, "โ Fehler beim Herunterladen der Audiodatei")
return
filepath, mime_type = result
logger.info(f"โ
Audio gespeichert: {filepath}")
transcription = await self._transcribe_audio(filepath)
await self.bot.send_message(room.room_id, f"๐ค Transkription:\n\n{transcription}")
except Exception as e:
logger.error(f"Fehler bei Audioverarbeitung: {e}")
await self.bot.send_message(room.room_id, f"โ Fehler: {str(e)}")
async def handle_file(self, room, event):
"""Handler fรผr Datei-Events"""
try:
logger.info(f"๐ Datei empfangen: {event.body}")
result = await self.media_handler.download_media(event.url, event.body)
if not result:
await self.bot.send_message(room.room_id, "โ Fehler beim Herunterladen der Datei")
return
filepath, mime_type = result
logger.info(f"โ
Datei gespeichert: {filepath}")
if mime_type == 'application/pdf':
analysis = await self._analyze_pdf(filepath)
elif mime_type in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword']:
analysis = await self._analyze_docx(filepath)
elif mime_type in ['application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint']:
analysis = await self._analyze_pptx(filepath)
else:
analysis = f"๐ Datei empfangen: {event.body}\nTyp: {mime_type}\nGrรถรe: {filepath.stat().st_size / 1024:.2f} KB"
await self.bot.send_message(room.room_id, f"๐ Dokument-Analyse:\n\n{analysis}")
except Exception as e:
logger.error(f"Fehler bei Dateiverarbeitung: {e}")
await self.bot.send_message(room.room_id, f"โ Fehler: {str(e)}")
async def _analyze_image(self, filepath: Path) -> str:
"""Analysiere Bild mit Vision Model"""
try:
import base64
with open(filepath, 'rb') as f:
image_data = base64.b64encode(f.read()).decode()
response = await self.bot.ollama_client.generate(
model=self.bot.config.vision_model,
prompt="Beschreibe dieses Bild detailliert auf Deutsch.",
images=[image_data]
)
return response.get('response', 'Keine Antwort erhalten')
except Exception as e:
logger.error(f"Vision-Analyse fehlgeschlagen: {e}")
return f"โ Bildanalyse fehlgeschlagen: {str(e)}"
async def _transcribe_audio(self, filepath: Path) -> str:
"""Transkribiere Audio"""
return "๐ง Audio-Transkription wird noch implementiert"
async def _analyze_pdf(self, filepath: Path) -> str:
"""Analysiere PDF"""
try:
from PyPDF2 import PdfReader
reader = PdfReader(str(filepath))
num_pages = len(reader.pages)
text = ""
for i, page in enumerate(reader.pages[:3]):
text += f"\n--- Seite {i+1} ---\n{page.extract_text()}\n"
prompt = f"Fasse dieses PDF-Dokument zusammen:\n\n{text[:2000]}"
response = await self.bot.ollama_client.generate(
model=self.bot.config.model,
prompt=prompt
)
summary = response.get('response', 'Keine Antwort')
return f"๐ PDF: {filepath.name}\nSeiten: {num_pages}\n\nZusammenfassung:\n{summary}"
except Exception as e:
return f"โ PDF-Analyse fehlgeschlagen: {str(e)}"
async def _analyze_docx(self, filepath: Path) -> str:
"""Analysiere DOCX"""
try:
from docx import Document
doc = Document(str(filepath))
text = "\n".join([para.text for para in doc.paragraphs[:20]])
prompt = f"Fasse dieses Word-Dokument zusammen:\n\n{text[:2000]}"
response = await self.bot.ollama_client.generate(
model=self.bot.config.model,
prompt=prompt
)
summary = response.get('response', 'Keine Antwort')
return f"๐ DOCX: {filepath.name}\nAbsรคtze: {len(doc.paragraphs)}\n\nZusammenfassung:\n{summary}"
except Exception as e:
return f"โ DOCX-Analyse fehlgeschlagen: {str(e)}"
async def _analyze_pptx(self, filepath: Path) -> str:
"""Analysiere PPTX"""
try:
from pptx import Presentation
prs = Presentation(str(filepath))
slides_text = []
for i, slide in enumerate(prs.slides[:5], 1):
text = []
for shape in slide.shapes:
if hasattr(shape, "text"):
text.append(shape.text)
slides_text.append(f"Folie {i}: {' '.join(text)}")
content = "\n".join(slides_text)
prompt = f"Fasse diese Prรคsentation zusammen:\n\n{content[:2000]}"
response = await self.bot.ollama_client.generate(
model=self.bot.config.model,
prompt=prompt
)
summary = response.get('response', 'Keine Antwort')
return f"๐ PPTX: {filepath.name}\nFolien: {len(prs.slides)}\n\nZusammenfassung:\n{summary}"
except Exception as e:
return f"โ PPTX-Analyse fehlgeschlagen: {str(e)}"
EOF
# ============================================
# 5. MAIN BOT
# ============================================
cat > jean/core/bot.py << 'EOF'
"""
Jean Matrix Bot - Main Bot File
"""
import asyncio
import logging
from nio import (
AsyncClient,ย
MatrixRoom,ย
RoomMessageText,
RoomMessageImage,
RoomMessageAudio,
RoomMessageFile,
InviteEvent,
SyncResponse,
LoginResponse
)
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from jean.core.config import Config
from jean.core.media_handler import MediaHandler, MediaEventHandler
from jean.utils.ollama_client import OllamaClient
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class JeanBot:
"""Jean Matrix Bot"""
def __init__(self):
self.config = Config()
self.client = None
self.ollama_client = OllamaClient(
base_url=self.config.ollama_url,
model=self.config.model,
vision_model=self.config.vision_model
)
self.media_handler = None
self.media_event_handler = None
self.chat_history = {}
async def setup(self):
"""Initialize bot"""
logger.info("๐ Jean Bot wird gestartet...")
self.client = AsyncClient(
self.config.homeserver,
self.config.user
)
# Login if needed
if self.config.access_token:
self.client.access_token = self.config.access_token
self.client.device_id = self.config.device_id
elif self.config.password:
logger.info("๐ Logging in with password...")
response = await self.client.login(self.config.password)
if isinstance(response, LoginResponse):
logger.info("โ
Login successful")
logger.info(f"๐พ Save this token to .env: {response.access_token}")
else:
raise Exception(f"Login failed: {response}")
else:
raise Exception("No access_token or password provided")
# Initialize handlers
self.media_handler = MediaHandler(self.client)
self.media_event_handler = MediaEventHandler(self)
# Register callbacks
self.client.add_event_callback(self.on_message, RoomMessageText)
self.client.add_event_callback(self.on_image, RoomMessageImage)
self.client.add_event_callback(self.on_audio, RoomMessageAudio)
self.client.add_event_callback(self.on_file, RoomMessageFile)
self.client.add_event_callback(self.on_invite, InviteEvent)
logger.info(f"โ
Bot konfiguriert als {self.config.user}")
async def start(self):
"""Start bot"""
try:
await self.setup()
sync_response = await self.client.sync(timeout=30000)
if not isinstance(sync_response, SyncResponse):
logger.error(f"โ Sync fehlgeschlagen: {sync_response}")
return
logger.info("โ
Verbindung zu Matrix erfolgreich")
logger.info(f"๐ In {len(self.client.rooms)} Rรคumen")
asyncio.create_task(self._cleanup_loop())
await self.sync_loop()
except Exception as e:
logger.error(f"โ Start error: {e}", exc_info=True)
finally:
await self.client.close()
async def sync_loop(self):
"""Main sync loop"""
logger.info("๐ Sync loop started")
while True:
try:
await self.client.sync_forever(timeout=30000, full_state=True)
except Exception as e:
logger.error(f"โ Sync error: {e}")
await asyncio.sleep(5)
async def on_message(self, room: MatrixRoom, event: RoomMessageText):
"""Text message handler"""
if event.sender == self.client.user:
return
message = event.body.strip()
is_dm = len(room.users) == 2
is_mentioned = self.config.user in message
if not (is_dm or is_mentioned or message.startswith(self.config.command_prefix)):
return
logger.info(f"๐ฌ [{room.display_name}] {event.sender}: {message}")
if message.startswith(self.config.command_prefix):
await self.handle_command(room, event, message)
else:
await self.handle_chat(room, event, message)
async def on_image(self, room: MatrixRoom, event: RoomMessageImage):
"""Image handler"""
if event.sender == self.client.user:
return
logger.info(f"๐ผ๏ธ Image received: {event.body}")
await self.media_event_handler.handle_image(room, event)
async def on_audio(self, room: MatrixRoom, event: RoomMessageAudio):
"""Audio handler"""
if event.sender == self.client.user:
return
logger.info(f"๐ต Audio received: {event.body}")
await self.media_event_handler.handle_audio(room, event)
async def on_file(self, room: MatrixRoom, event: RoomMessageFile):
"""File handler"""
if event.sender == self.client.user:
return
logger.info(f"๐ File received: {event.body}")
await self.media_event_handler.handle_file(room, event)
async def on_invite(self, room: MatrixRoom, event: InviteEvent):
"""Invite handler"""
logger.info(f"๐จ Invite from {event.sender} to {room.room_id}")
try:
await self.client.join(room.room_id)
logger.info(f"โ
Joined room: {room.room_id}")
await self.send_message(
room.room_id,
f"๐ Hallo! Ich bin Jean, dein KI-Assistent.\n\nSende `{self.config.command_prefix}help` fรผr Hilfe."
)
except Exception as e:
logger.error(f"โ Join error: {e}")
async def handle_command(self, room: MatrixRoom, event: RoomMessageText, message: str):
"""Handle commands"""
parts = message[1:].split(maxsplit=1)
command = parts[0].lower()
args = parts[1] if len(parts) > 1 else ""
if command == "help":
await self.cmd_help(room)
elif command == "ping":
await self.send_message(room.room_id, "๐ Pong!")
elif command == "status":
await self.cmd_status(room)
elif command == "chat":
await self.handle_chat(room, event, args)
elif command == "reset":
self.chat_history[room.room_id] = []
await self.send_message(room.room_id, "๐ Chat-Verlauf gelรถscht!")
else:
await self.send_message(
room.room_id,
f"โ Unbekannter Command: {command}\nSende `!help` fรผr Hilfe."
)
async def cmd_help(self, room: MatrixRoom):
"""Help command"""
help_text = f"""๐ Jean Bot - Hilfe
Commands:
โข {self.config.command_prefix}help - Diese Hilfe
โข {self.config.command_prefix}ping - Test
โข {self.config.command_prefix}chat <text> - Chat mit KI
โข {self.config.command_prefix}reset - Chat-Verlauf lรถschen
โข {self.config.command_prefix}status - Bot-Status
Multimedia:
โข ๐ผ๏ธ Bilder โ Automatische Analyse
โข ๐ต Audio โ Transkription
โข ๐ Dateien โ Dokument-Analyse (PDF, DOCX, PPTX)
Tipps:
โข In DMs kannst du direkt ohne Command schreiben
โข In Gruppen erwรคhne mich mit @jean
โข Sende Bilder fรผr Vision-Analyse
"""
await self.send_message(room.room_id, help_text)
async def cmd_status(self, room: MatrixRoom):
"""Status command"""
try:
import psutil
process = psutil.Process()
memory_mb = process.memory_info().rss / 1024 / 1024
memory_info = f"๐พ Memory: {memory_mb:.1f} MB\n"
except:
memory_info = ""
ollama_status = "โ
Online"
try:
models = await self.ollama_client.list_models()
model_info = f"\nModelle: {', '.join(models[:3])}" if models else ""
except:
ollama_status = "โ Offline"
model_info = ""
status_text = f"""๐ Bot Status
๐ค Jean Bot: Online
๐ Homeserver: {self.config.homeserver}
๐ค User: {self.config.user}
๐ Rรคume: {len(self.client.rooms)}
๐ง Ollama: {ollama_status}{model_info}
{memory_info}
"""
await self.send_message(room.room_id, status_text)
async def handle_chat(self, room: MatrixRoom, event: RoomMessageText, message: str):
"""Chat with AI"""
try:
message = message.replace(self.config.user, "").strip()
if not message:
return
if room.room_id not in self.chat_history:
self.chat_history[room.room_id] = []
history = self.chat_history[room.room_id]
history.append({"role": "user", "content": message})
if len(history) > 10:
history = history[-10:]
await self.client.room_typing(room.room_id, typing_state=True)
response = await self.ollama_client.chat(
messages=history,
model=self.config.model
)
reply = response.get('message', {}).get('content', 'Keine Antwort erhalten')
history.append({"role": "assistant", "content": reply})
self.chat_history[room.room_id] = history
await self.send_message(room.room_id, reply)
except Exception as e:
logger.error(f"โ Chat error: {e}")
await self.send_message(room.room_id, f"โ Fehler: {str(e)}")
finally:
await self.client.room_typing(room.room_id, typing_state=False)
async def send_message(self, room_id: str, message: str):
"""Send message"""
try:
await self.client.room_send(
room_id=room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": message
}
)
except Exception as e:
logger.error(f"โ Send error: {e}")
async def _cleanup_loop(self):
"""Periodic cleanup"""
while True:
try:
await asyncio.sleep(3600)
await self.media_handler.cleanup_old_files(max_age_hours=24)
except Exception as e:
logger.error(f"Cleanup error: {e}")
async def main():
"""Main function"""
bot = JeanBot()
await bot.start()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("๐ Bot wird beendet...")
EOF
# ============================================
# 6. __init__ FILES
# ============================================
cat > jean/__init__.py << 'EOF'
"""Jean Matrix Bot Package"""
__version__ = "1.0.0"
EOF
cat > jean/core/__init__.py << 'EOF'
"""Jean Core Package"""
EOF
cat > jean/utils/__init__.py << 'EOF'
"""Jean Utils Package"""
from .ollama_client import OllamaClient
__all__ = ['OllamaClient']
EOF
cat > jean/plugins/__init__.py << 'EOF'
"""Jean Plugins Package"""
EOF
cat > jean/plugins/builtin/__init__.py << 'EOF'
"""Builtin Plugins"""
EOF
cat > jean/plugins/custom/__init__.py << 'EOF'
"""Custom Plugins"""
EOF
# ============================================
# 7. .env.example
# ============================================
cat > .env.example << 'EOF'
# ========================================
# Jean Matrix Bot Configuration
# ========================================
# SETUP: Run python3 get_token.py to auto-fill these values
# Matrix Server Configuration
MATRIX_HOMESERVER=https://matrix.rs-servertech.com
MATRIX_USER=@jean:matrix.rs-servertech.com
MATRIX_ACCESS_TOKEN=your_token_here
MATRIX_DEVICE_ID=JEAN_BOT_001
# Alternative: Use Password (not recommended)
# MATRIX_PASSWORD=your_password_here
# Ollama AI Configuration
OLLAMA_URL=http://192.168.10.210:11434
OLLAMA_MODEL=llama3.2:latest
OLLAMA_VISION_MODEL=llava:latest
# Bot Settings
COMMAND_PREFIX=!
LOG_LEVEL=INFO
DATABASE_URL=sqlite:///data/jean.db
# Admin Users (comma-separated)
ADMIN_USERS=@admin:matrix.rs-servertech.com
# Feature Flags
ENABLE_IMAGE_ANALYSIS=true
ENABLE_VOICE_TRANSCRIPTION=false
ENABLE_DOCUMENT_ANALYSIS=true
# Rate Limiting
RATE_LIMIT_MESSAGES=10
RATE_LIMIT_WINDOW=60
EOF
# ============================================
# 8. requirements.txt (KOMPLETT)
# ============================================
cat > requirements.txt << 'EOF'
# Matrix Client
matrix-nio[e2e]==0.24.0
# HTTP & Async
aiohttp==3.9.1
aiofiles==23.2.1
# Database
sqlalchemy[asyncio]==2.0.23
aiosqlite==0.19.0
# Configuration & Logging
python-dotenv==1.0.0
pyyaml==6.0.1
python-json-logger==2.0.7
# System Utils
psutil==5.9.6
# Document Processing
PyPDF2==3.0.1
python-docx==1.1.0
python-pptx==0.6.23
beautifulsoup4==4.12.2
lxml==5.1.0
# Image Processing
Pillow==10.1.0
# Data Processing
pandas==2.1.4
numpy==1.26.2
# Additional
requests==2.31.0
EOF
# ============================================
# 9. setup.sh
# ============================================
cat > setup.sh << 'EOFSETUP'
#!/bin/bash
set -e
echo "๐ Jean Matrix Bot Setup"
echo ""
# Check Python
if ! command -v python3 &> /dev/null; then
echo "โ Python 3 is required but not installed."
exit 1
fi
# Create venv
if [ ! -d "venv" ]; then
echo "๐ฆ Creating virtual environment..."
python3 -m venv venv
fi
# Activate venv
source venv/bin/activate
# Install dependencies
echo "๐ฅ Installing dependencies..."
pip install -q --upgrade pip
pip install -q -r requirements.txt
# Create .env if not exists
if [ ! -f ".env" ]; then
echo "๐ Creating .env file..."
cp .env.example .env
fi
echo ""
echo "โ
Setup complete!"
echo ""
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo "๐ NEXT STEPS:"
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo ""
echo "๐ OPTION 1: Auto-Generate Token (Recommended)"
echo "ย python3 get_token.py"
echo ""
echo "๐ง OPTION 2: Manual Token"
echo "ย 1. Get token from Element/Matrix client"
echo "ย 2. Edit .env: nano .env"
echo ""
echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
echo ""
echo "๐ After configuration, start with:"
echo "ย source venv/bin/activate"
echo "ย python -m jean.core.bot"
echo ""
EOFSETUP
chmod +x setup.sh
# ============================================
# 10. README.md
# ============================================
cat > README.md << 'EOFREADME'
# Jean Matrix Bot ๐ค
Vollstรคndiger Matrix-Bot mit KI-Integration, Multimedia-Analyse und Plugin-System.
## โจ Features
- ๐ค **KI-Chat** via Ollama (LLaMA, Mistral, etc.)
- ๐ผ๏ธ **Bildanalyse** mit Ollama Vision (LLaVA)
- ๐ต **Audio-Transkription** mit Whisper (optional)
- ๐ **Dokument-Analyse** (PDF, DOCX, PPTX)
- ๐ **Plugin-System** - einfach erweiterbar
- ๐ช **Auto-Join** bei Einladungen
- ๐ฌ **Smart Chat** - Erkennt DMs und Mentions
## ๐ Quick Start
### Automatische Installation (Empfohlen)
```bash
# 1. Setup ausfรผhren
./setup.sh
# 2. Token generieren (automatisch)
source venv/bin/activate
python3 get_token.py
# 3. Bot starten
python -m jean.core.bot
```
### Manuelle Installation
```bash
# 1. Virtual Environment
python3 -m venv venv
source venv/bin/activate
# 2. Dependencies
pip install -r requirements.txt
# 3. Configuration
cp .env.example .env
nano .envย # Token einfรผgen
# 4. Start
python -m jean.core.bot
```
## ๐ Authentifizierung
### Auto-Generate Token (Empfohlen)
```bash
python3 get_token.py
```
Das Script fragt interaktiv nach:
- Homeserver URL
- Username
- Password
- Device Name
Und schreibt den Token automatisch in `.env`!
### Manueller Token
1. In Element: Settings โ Help & About โ Advanced โ Access Token
2. Token in `.env` einfรผgen
## ๐ฎ Commands
- `!help` - Zeigt alle verfรผgbaren Commands
- `!ping` - Testet Bot-Antwort
- `!chat <text>` - Chat mit KI
- `!reset` - Chat-Verlauf lรถschen
- `!status` - Bot-Status anzeigen
### Multimedia
- **Bilder senden** โ Automatische Analyse
- **Audio senden** โ Transkription
- **Dateien senden** โ Dokument-Analyse
## ๐ Projektstruktur
```
jean-matrix-bot/
โโโ jean/
โย โโโ core/ย # Bot-Kern
โย โโโ plugins/ย # Erweiterungen
โย โโโ utils/ย # Hilfsfunktionen
โโโ config/
โโโ data/ย # Datenbank
โโโ logs/ย # Log-Dateien
โโโ get_token.pyย # Token-Generator
โโโ setup.shย # Setup-Script
โโโ requirements.txt
โโโ README.md
```
## โ๏ธ Konfiguration
Alle Einstellungen in `.env`:
```env
MATRIX_HOMESERVER=https://your-server.com
MATRIX_USER=@jean:your-server.com
MATRIX_ACCESS_TOKEN=syt_xxxxx
OLLAMA_URL=http://localhost:11434
```
## ๐ง Troubleshooting
**Bot antwortet nicht:**
```bash
# Prรผfe Token
python3 get_token.py
# Prรผfe Ollama
curl http://localhost:11434/api/tags
```
**Bildanalyse funktioniert nicht:**
```bash
# Installiere llava model
ollama pull llava
```
## ๐ System-Anforderungen
- Python 3.11+
- 2GB RAM (minimal)
- Ollama Server (fรผr KI)
- Matrix Homeserver Zugang
## ๐ Lizenz
MIT License
---
**Made with โค๏ธ for Matrix**
EOFREADME
echo ""
echo "โ
====================================="
echo "โ
PROJECT COMPLETE!"
echo "โ
====================================="
echo ""
echo "๐ฆ Creating archive..."
cd ..
tar -czf "${PROJECT}.tar.gz" "$PROJECT"
echo ""
echo "โ
FINISHED!"
echo ""
echo "๐ Project: $PROJECT"
echo "๐ฆ Archive: ${PROJECT}.tar.gz"
echo ""
echo "๐ Created Files:"
echo "ย โ
get_token.py - Token Generator"
echo "ย โ
jean/core/config.py - Configuration"
echo "ย โ
jean/core/bot.py - Main Bot"
echo "ย โ
jean/core/media_handler.py - Media Handler (FIXED)"
echo "ย โ
jean/utils/ollama_client.py - Ollama Client"
echo "ย โ
setup.sh - Setup Script"
echo "ย โ
requirements.txt - Dependencies"
echo "ย โ
README.md - Documentation"
echo ""
echo "๐ Quick Start:"
echo "ย cd $PROJECT"
echo "ย ./setup.sh"
echo "ย python3 get_token.py"
echo "ย python -m jean.core.bot"
echo ""
ENDOFSCRIPT
chmod +x create_jean_complete_fixed.sh
./create_jean_complete_fixed.sh
bash
# 1. Test ob Matrix-Server erreichbar ist
curl -I https://matrix.rs-servertech.com
# 2. Test Login mit curl
curl -X POST https://matrix.rs-servertech.com/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{
"type": "m.login.password",
"user": "jean",
"password": "DEIN_PASSWORD"
}'