commit d5b4651062fc24361b5f09d69d28de8c73a9ca31
parent 761400df1333b9f66f0005d2f4f95a3ad19452f2
Author: Nathaniel Chappelle <nathaniel@chappelle.dev>
Date: Thu, 5 Feb 2026 15:18:46 -0800
Initial commit, it basically works as a bot
Diffstat:
1 file changed, 116 insertions(+), 12 deletions(-)
diff --git a/listen-page-bot/bot.py b/listen-page-bot/bot.py
@@ -1,21 +1,125 @@
+import os
+import requests
+import frontmatter
+from bs4 import BeautifulSoup
from telegram import Update
-from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
+from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes, ConversationHandler
from dotenv import load_dotenv
-import os
-# Load dotenv variables, such as the Telegram token
+# --- CONFIGURATION ---
load_dotenv()
+REPO_PATH = os.getenv("REPO_PATH")
+MD_FILE_PATH = os.path.join(REPO_PATH, "content/listen.md")
+IMG_DIR = os.path.join(REPO_PATH, "content/assets/covers")
+MAX_SONGS = 9
+
+# Conversation states
+TITLE, ARTIST, LINK, COVER = range(4)
+
+async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ await update.message.reply_text("Let's add a new song! What is the Song Title?")
+ return TITLE
+
+async def get_title(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ context.user_data['title'] = update.message.text
+ await update.message.reply_text(f"Artist for '{update.message.text}'?")
+ return ARTIST
+
+async def get_artist(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ context.user_data['artist'] = update.message.text
+ await update.message.reply_text("Paste the YouTube Music link:")
+ return LINK
+
+async def get_link(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ context.user_data['link'] = update.message.text
+ await update.message.reply_text("Finally, send the Album Cover image.")
+ return COVER
+
+async def get_cover(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ # Get the highest resolution photo
+ photo_file = await update.message.photo[-1].get_file()
+
+ # Generate filename (e.g., song_name.jpg)
+ safe_name = "".join([c for c in context.user_data['title'] if c.isalnum()]).lower()
+ img_filename = f"{safe_name}.jpg"
+ img_path = os.path.join(IMG_DIR, img_filename)
+
+ # Ensure directory exists
+ os.makedirs(IMG_DIR, exist_ok=True)
+
+ # Download image
+ await photo_file.download_to_drive(img_path)
+
+ # Update the Markdown file
+ update_markdown(
+ title=context.user_data['title'],
+ artist=context.user_data['artist'],
+ link=context.user_data['link'],
+ img_url=f"/assets/covers/{img_filename}" # Relative path for HTML
+ )
+
+ await update.message.reply_text("Successfully updated listen.md and saved cover!")
+ return ConversationHandler.END
+
+def update_markdown(title, artist, link, img_url):
+ # Load the MD file
+ post = frontmatter.load(MD_FILE_PATH)
+
+ # Use BeautifulSoup to parse the HTML within the markdown content
+ soup = BeautifulSoup(post.content, 'html.parser')
+ gallery = soup.find('section', class_='song-gallery')
+
+ if not gallery:
+ # Fallback if section doesn't exist
+ return
+
+ # Create new song card HTML
+ new_card_html = f'''
+ <div class="song-card">
+ <img src="{img_url}" alt="{title} Album Art">
+ <div class="song-info">
+ <span class="song-title">{title}</span>
+ <span class="artist-name">{artist}</span>
+ <a href="{link}" class="song-link">Listen</a>
+ </div>
+ </div>'''
+ new_card_soup = BeautifulSoup(new_card_html, 'html.parser')
+
+ # Add new card to the top (FIFO behavior)
+ gallery.insert(0, new_card_soup)
+
+ # Enforce MAX_SONGS (9)
+ cards = gallery.find_all('div', class_='song-card')
+ if len(cards) > MAX_SONGS:
+ for extra in cards[MAX_SONGS:]:
+ extra.decompose()
-async def hello(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- await update.message.reply_text(f'Hello {update.effective_user.first_name}')
+ # Format the content back (BeautifulSoup adds extra formatting,
+ # so we clean up the string slightly)
+ post.content = f"\nAn overview of music and podcasts I currently have on repeat.\n\n## Music\n\n{gallery.prettify()}"
+
+ # Save file
+ with open(MD_FILE_PATH, 'wb') as f:
+ frontmatter.dump(post, f)
-telegram_token = os.getenv("TELEGRAM_TOKEN")
-if telegram_token == None:
- print("No Telegram token found in dotenv")
- exit()
+async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ await update.message.reply_text("Cancelled.")
+ return ConversationHandler.END
-app = ApplicationBuilder().token(telegram_token).build()
+if __name__ == '__main__':
+ app = ApplicationBuilder().token(os.getenv("TELEGRAM_TOKEN")).build()
-app.add_handler(CommandHandler("hello", hello))
+ conv_handler = ConversationHandler(
+ entry_points=[CommandHandler('add', start)],
+ states={
+ TITLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_title)],
+ ARTIST: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_artist)],
+ LINK: [MessageHandler(filters.TEXT & ~filters.COMMAND, get_link)],
+ COVER: [MessageHandler(filters.PHOTO, get_cover)],
+ },
+ fallbacks=[CommandHandler('cancel', cancel)],
+ )
-app.run_polling()
+ app.add_handler(conv_handler)
+ print("Bot is running...")
+ app.run_polling()