Intro
Have you ever wanted to share the lyrics of a song with your friends in telegram dialogue, but you were too lazy to open a browser and search for the lyrics manually? With this bot, you just need to enter the name in inline mode and select the result from the inline menu with search results.
P.S. This is also my first article, so I'd love to see any feedback.
Contents
- Getting API keys
- Creating the bot
- Using the bot
Getting API keys
Telegram BotAPI
Firstly, let's create bot in @BotFather.
Grab your token, we will use it later.
Our bot will work in inline mode. This means that the user just needs to write a @your_bot_name and a query to get the search results with song names.
Enbaling inline mode:
Then select your bot from keyboard.
Example results:
We also need to activate the inline feedback setting so that the bot can load the lyrics of the song into the message (more on that later)
Example results:
Genius API
Firstly, login to your genius.com account or register if you don't have one.
Then go to the https://genius.com/api-clients and grab the "Client access token":
Creating the bot
For this bot we'll use aiogram - asynchronous framework for creating fast and structured telegram bots, also we need lyricsgenius lib for interacting with Genius API and pydantic_settings to create config script.
pip install aiogram lyricsgenius pydantic_settings
In aiogram you have 3 main parts of your project: Bot, Dispatcher and handlers.
Bot - Telegram bot instance, you need token to get your bot.
Dispatcher - an object that receives updates from Telegram and then selects a handler to process the received update.
Handler - an asynchronous function that receives the next update from the dispatcher/router and processes it. You need to register it in the dispatcher. You can place handlers inside different Routers that act as a dispatcher for a subset of handlers.
Let's create basic bot with simple functionality.
Config
In your project directory create file .env and fill it like this:
BOT_TOKEN='PASTE YOUR BOT TOKEN'
GENIUS_TOKEN='PASTE YOUR CLIENT ACCESS TOKEN'
To use this variables we need to create config file.
Our config.py file should look like this:
from typing import Optional
from pydantic_settings import BaseSettings
from pydantic import SecretStr
class Settings(BaseSettings):
bot_token: SecretStr
genius_token: SecretStr
class Config:
env_file = "./.env"
env_file_encoding = "utf-8"
env_nested_delimiter = "__"
CONFIG = Settings()
Basic handlers
We can now securely access our tokens, let's create basic bot in main.py:
import asyncio
from aiogram import Bot, Dispatcher
from config import CONFIG
bot = Bot(token=CONFIG.bot_token.get_secret_value())
async def main():
dp = Dispatcher()
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
At this moment we have no handlers, so bot won't react to any messages.
To create basic handlers, create folder handlers/ and file base.py in it at your project directory.
File structure should look like this:
├── main.py
├── config.py
├── .env
├── handlers
│ └── base.py
In base.py, firstly we import Router to create router, filter Command so bot can react to some commands and type Message:
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
router = Router()
Then we register handler functions by wrapping them in special decorators.
@router.message(Command("start"))
async def start_command(message: Message):
await message.answer("This bot can search and send song lyrics from genius.com\nFor more help use /help")
@router.message(Command("help"))
async def help_command(message: Message):
await message.answer("Use this bot in inline mode. Just type @lyrics_genius_bot <song name> and results will appear soon!")
"@router.message" means that bot will react at messages, and filter Command("start") as argument for decorator means that handler will be executed only if message contains command "/start"
Finally, import router in main.py and register it:
from handlers import base
async def main():
dp = Dispatcher()
dp.include_routers(
base.router,
)
await dp.start_polling(bot)
Congrats! We made basic bot.
Genius API utilite
Now let's create utilite to search songs lyrics along genis.com:
from config import CONFIG
import lyricsgenius as lg
class LyricsFinder:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(LyricsFinder, cls).__new__(cls)
cls.__instance.init()
return cls.__instance
def init(self):
self.genius = lg.Genius(CONFIG.genius_token.get_secret_value(),
timeout=40)
async def search_song(self, name="", page=1):
result = self.genius.search_songs(name, per_page=5, page=page)
return result['hits']
# current song: result['hits'][0<N<=5]["result"]
# ["full_title"] or ["header_image_thumbnail_url"] or just ["id"]
async def get_lyrics(self, song_id=None):
return str(self.genius.lyrics(song_id))
This class is built upon Singelton pattern and describes connection to Genius API. It has two methods: for searching songs by query and for getting lyrics of songs by id.
Inline handlers
Bot should receive inline queries, perform search and return user a list of results. When user chooses result from list, bot should send message "Loading..." with inline button that contains link to song on genius.com
To create that button we need to make inline keyboard with dynamic parameters, so button will contain song's title and link. Create folder keyboards/ and file song_url.py in it. Also create file songs_inline.py in handlers/ so we can create router for inline queries.
Final file structure
├── main.py
├── config.py
├── .env
├── handlers
│ ├── base.py
│ └── songs_inline.py
├── keyboards
│ ├── song_url.py
├── utils
│ └── genius_api.py
song_url.py:
from aiogram.utils.keyboard import InlineKeyboardBuilder
def song_url_button(url=None, title=None):
builder = InlineKeyboardBuilder()
builder.button(text=title,
url=url)
return builder
songs_inline.py
from uuid import uuid4
import main
from aiogram import Router
from aiogram.types import InlineQuery, InlineQueryResultArticle, \
InputTextMessageContent, ChosenInlineResult
import lyricsgenius as lg
from utils.genius_api import LyricsFinder
from config import CONFIG
from keyboards import song_url
router = Router()
finder = LyricsFinder()
@router.inline_query()
async def show_results(inline_query: InlineQuery):
songs = await finder.search_song(inline_query.query)
results = []
for song in songs:
results.append(InlineQueryResultArticle(
id=str(song['result']["id"]),
title=song['result']["title"],
description=song["result"]["artist_names"],
input_message_content=InputTextMessageContent(
message_text="Loading text...",
),
reply_markup=song_url.song_url_button(url=song['result']["url"],
title=song['result']["full_title"]).as_markup(),
thumbnail_url=song["result"]["header_image_thumbnail_url"]
))
await inline_query.answer(results, is_personal=True)
@router.chosen_inline_result()
async def load_lyrics(
chosen_result: ChosenInlineResult,
):
l = str(await finder.get_lyrics(int(chosen_result.result_id)))
message = await main.bot.edit_message_text(
inline_message_id=chosen_result.inline_message_id,
text=l.split("\n", 1)[1]
)
Finally, register the router:
from handlers import base, songs_inline
async def main():
dp = Dispatcher()
dp.include_routers(
base.router,
songs_inline.router
)
await dp.start_polling(bot)
Using the bot
Startup bot:
python main.py
Example usage:
You can get all source code in my GitHub repository
Thank you for reading! ❤️ ❤️ ❤️
Top comments (1)
пошли в тг срочно