bots

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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:
Mlisten-page-bot/bot.py | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
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()