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
This commit is contained in:
2026-04-29 20:18:06 -05:00
parent 57a9944d14
commit 172e68dd80
3 changed files with 282 additions and 0 deletions
+14
View File
@@ -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 <username>` - 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.
+26
View File
@@ -358,6 +358,32 @@ Search Exploit-DB for security vulnerabilities and exploits. Returns detailed in
<em>Note: SSLv2/SSLv3 testing limited by Python security features</em></p>
</details>
<details><summary>🎵 <strong>Last.fm Integration</strong></summary>
<p>Register your Last.fm username and display currently playing tracks.</p>
<p><strong>Commands:</strong></p>
<ul>
<li><code>!register <username></code> - Register your Last.fm username</li>
<li><code>!np</code> - Display your currently playing track from Last.fm</li>
</ul>
<p><strong>Features:</strong></p>
<ul>
<li>Associate your Matrix ID with your Last.fm username</li>
<li>Display currently playing tracks with artist and album information</li>
<li>Uses SQLite database to store user associations</li>
<li>Requires LASTFM_API_KEY environment variable</li>
</ul>
<p><strong>Examples:</strong></p>
<ul>
<li><code>!register your_lastfm_username</code> - Register your Last.fm username</li>
<li><code>!np</code> - Show your currently playing track</li>
</ul>
<p><strong>Requirements:</strong></p>
<ul>
<li>LASTFM_API_KEY in .env file</li>
<li>Last.fm account at last.fm</li>
</ul>
</details>
<details><summary>📸 <strong>!sd [prompt]</strong></summary>
+242
View File
@@ -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 <lastfm_username>"
)
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 <username>\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}")