From 172e68dd80fb4264fa00b826389ec2c70097e4f5 Mon Sep 17 00:00:00 2001 From: Hash Borgir Date: Wed, 29 Apr 2026 20:18:06 -0500 Subject: [PATCH] Add Last.fm integration plugin with YouTube link support - Added Last.fm plugin for tracking currently playing music with !register and !np commands - Added YouTube API integration to provide links to currently playing songs - Updated help.py and README.md with documentation for the new plugin - Plugin stores user associations in SQLite database - Added YouTube API key to .env file for YouTube search functionality - Supports displaying track name in bold text with album information when available - Added YouTube link to songs when available --- README.md | 14 +++ plugins/help.py | 26 +++++ plugins/lastfm.py | 242 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 plugins/lastfm.py diff --git a/README.md b/README.md index 1a54837..37323c9 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,20 @@ Perform IP geolocation lookups with detailed geographic information. - ISP/Organization - Autonomous System Number (ASN) +### 🎵 Last.fm Integration + +**🎵 !register [username] and !np** +Register your Last.fm username and display currently playing tracks. + +**Features:** +- Associate Matrix ID with Last.fm username +- Display currently playing tracks +- Uses SQLite database for user associations + +**Commands:** +- `!register ` - Register your Last.fm username +- `!np` - Display currently playing track + ### ExploitDB Plugin A security plugin that searches Exploit-DB for vulnerabilities and exploits directly from Matrix. diff --git a/plugins/help.py b/plugins/help.py index 7965ad2..7f98a0a 100644 --- a/plugins/help.py +++ b/plugins/help.py @@ -358,6 +358,32 @@ Search Exploit-DB for security vulnerabilities and exploits. Returns detailed in Note: SSLv2/SSLv3 testing limited by Python security features

+
🎵 Last.fm Integration +

Register your Last.fm username and display currently playing tracks.

+

Commands:

+
    +
  • !register - Register your Last.fm username
  • +
  • !np - Display your currently playing track from Last.fm
  • +
+

Features:

+
    +
  • Associate your Matrix ID with your Last.fm username
  • +
  • Display currently playing tracks with artist and album information
  • +
  • Uses SQLite database to store user associations
  • +
  • Requires LASTFM_API_KEY environment variable
  • +
+

Examples:

+
    +
  • !register your_lastfm_username - Register your Last.fm username
  • +
  • !np - Show your currently playing track
  • +
+

Requirements:

+
    +
  • LASTFM_API_KEY in .env file
  • +
  • Last.fm account at last.fm
  • +
+
+
📸 !sd [prompt] diff --git a/plugins/lastfm.py b/plugins/lastfm.py new file mode 100644 index 0000000..970fb71 --- /dev/null +++ b/plugins/lastfm.py @@ -0,0 +1,242 @@ +""" +This plugin provides Last.fm integration for the bot. +It allows users to register their Last.fm username and display their currently playing track. +""" + +import logging +import os +import sqlite3 +import simplematrixbotlib as botlib +import aiosqlite +import json +import aiohttp + +# Database file path +DB_PATH = "lastfm.db" + +async def init_db(): + """Initialize the database with the required tables.""" + async with aiosqlite.connect(DB_PATH) as db: + await db.execute(""" + CREATE TABLE IF NOT EXISTS user_lastfm ( + matrix_user TEXT PRIMARY KEY, + lastfm_user TEXT NOT NULL + ) + """) + await db.commit() + +async def get_lastfm_username(matrix_user): + """Get Last.fm username for a Matrix user.""" + async with aiosqlite.connect(DB_PATH) as db: + async with db.execute("SELECT lastfm_user FROM user_lastfm WHERE matrix_user = ?", (matrix_user,)) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + +async def set_lastfm_username(matrix_user, lastfm_user): + """Associate a Last.fm username with a Matrix user.""" + async with aiosqlite.connect(DB_PATH) as db: + # Check if user already exists + async with db.execute("SELECT lastfm_user FROM user_lastfm WHERE matrix_user = ?", (matrix_user,)) as cursor: + row = await cursor.fetchone() + + if row: + # Update existing record + await db.execute("UPDATE user_lastfm SET lastfm_user = ? WHERE matrix_user = ?", (lastfm_user, matrix_user)) + else: + # Insert new record + await db.execute("INSERT INTO user_lastfm (matrix_user, lastfm_user) VALUES (?, ?)", (matrix_user, lastfm_user)) + + await db.commit() + +async def get_recent_track(lastfm_user): + """ + Get the recent tracks for a Last.fm user. + + Args: + lastfm_user (str): Last.fm username + + Returns: + dict: Track information or None if error + """ + api_key = os.getenv("LASTFM_API_KEY") + if not api_key: + logging.error("LASTFM_API_KEY not found in environment variables") + return None + + url = "http://ws.audioscrobbler.com/2.0/" + params = { + "method": "user.getrecenttracks", + "user": lastfm_user, + "api_key": api_key, + "format": "json", + "limit": "1" + } + + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, params=params) as response: + if response.status == 200: + data = await response.json() + return data + else: + logging.error(f"Last.fm API returned status {response.status}") + return None + except Exception as e: + logging.error(f"Error querying Last.fm API: {e}") + return None + +async def get_youtube_link(artist, track_name): + """ + Search for a YouTube link for the given artist and track using YouTube Data API. + + Args: + artist (str): Artist name + track_name (str): Track name + + Returns: + str: YouTube video URL or None if not found + """ + youtube_api_key = os.getenv("YOUTUBE_API_KEY") + if not youtube_api_key: + return None + + search_query = f"{artist} {track_name}" + url = "https://www.googleapis.com/youtube/v3/search" + params = { + "part": "snippet", + "q": search_query, + "type": "video", + "key": youtube_api_key, + "maxResults": "1" + } + + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, params=params) as response: + if response.status == 200: + data = await response.json() + items = data.get("items", []) + if items: + video_id = items[0].get("id", {}).get("videoId") + if video_id: + return f"https://www.youtube.com/watch?v={video_id}" + return None + except Exception as e: + logging.error(f"Error searching YouTube: {e}") + return None + +async def format_recent_track(matrix_user, lastfm_user, data): + """ + Format recent track information into a readable message. + + Args: + matrix_user (str): The Matrix user ID + lastfm_user (str): The Last.fm username + data (dict): Data from Last.fm API + + Returns: + str: Formatted message + """ + if not data or "error" in data: + return f"🔍 No recent track data found for {lastfm_user}." + + # Check if there's a currently playing track + tracks = data.get("recenttracks", {}).get("track", []) + if not tracks: + return f"🔍 No recent tracks found for {lastfm_user}." + + # Get the most recent track (first in the list) + track = tracks[0] if tracks else {} + + # Check if it's currently playing + now_playing = track.get("@attr", {}).get("nowplaying", "false") == "true" + + # Extract track information + artist = track.get("artist", {}).get("#text", "Unknown Artist") + name = track.get("name", "Unknown Track") + album = track.get("album", {}).get("#text", "") + + # Create one-line message with bold track name + if now_playing: + if album: + message = f"🎵 {matrix_user} is currently playing: **{name}** by {artist} from the album {album}" + else: + message = f"🎵 {matrix_user} is currently playing: **{name}** by {artist}" + elif tracks: + if album: + message = f"🎵 {matrix_user} last played: **{name}** by {artist} from the album {album}" + else: + message = f"🎵 {matrix_user} last played: **{name}** by {artist}" + else: + message = f"🔍 No recent tracks found for {lastfm_user}." + + # Try to get YouTube link + youtube_link = await get_youtube_link(artist, name) + if youtube_link: + message += f" | [YouTube Link]({youtube_link})" + + return message + +async def handle_command(room, message, bot, prefix, config): + """ + Function to handle the !register and !np commands. + + Args: + room (Room): The Matrix room where the command was invoked. + message (RoomMessage): The message object containing the command. + bot (Bot): The bot object. + prefix (str): The command prefix. + config (dict): Configuration parameters. + + Returns: + None + """ + match = botlib.MessageMatch(room, message, bot, prefix) + + # Initialize database on first run + await init_db() + + if match.is_not_from_this_bot() and match.prefix() and match.command("register"): + args = match.args() + + if len(args) < 1: + await bot.api.send_text_message( + room.room_id, + "Usage: !register " + ) + return + + lastfm_user = args[0].strip() + matrix_user = str(message.sender) + + # Register the Last.fm username + await set_lastfm_username(matrix_user, lastfm_user) + + # Confirm registration + await bot.api.send_text_message( + room.room_id, + f"✅ Registered Last.fm user {lastfm_user} for {matrix_user}" + ) + + logging.info(f"Registered Last.fm user {lastfm_user} for {matrix_user}") + + elif match.is_not_from_this_bot() and match.prefix() and match.command("np"): + # Get currently playing track + matrix_user = str(message.sender) + lastfm_user = await get_lastfm_username(matrix_user) + + if not lastfm_user: + await bot.api.send_text_message( + room.room_id, + f"Please register your Last.fm username first with !register \n" + f"Example: !register your_lastfm_username" + ) + return + + # Get recent track data + track_data = await get_recent_track(lastfm_user) + + # Format and send results + result_message = await format_recent_track(matrix_user, lastfm_user, track_data) + await bot.api.send_markdown_message(room.room_id, result_message) + logging.info(f"Sent Last.fm now playing information for {lastfm_user}") \ No newline at end of file