Очень долгое время слушал музыку непосредственно в Telegram. Там и пообщаться можно и проигрыватель довольно удобный. Но рано или поздно - каждый сталкивается с проблемой, когда свой плейлист надоедает и хочется чего-то нового. В YouTube Music есть довольно удобная функция, которая позволяет каждый день довольствоваться новой музыкой. Что я имею ввиду? При включении любой песни, далее проигрывается трек, который похож на предыдущий. Таким образом можно постоянно открывать для себя новые музыкальные горизонты :)
В Телеграмме у меня было сохранено более 1000 песен. Я решил написать бота, который будет считывать пересланную музыку из ТГ, создавать плейлист в YouTube и загружать все песни туда. Таким образом и чему-то новому научусь и песни загружу(ну и если кому-то из знакомых понадобятся услуги моего бота, это может сэкономить им время, хотя я мало верю, что кому-то это пригодится, это больше небольшой, учебный проектик).
Для начала нужно зайти в тг, и с помощью этого бота, создать своего:
![Здесь нам нужно скопировать токен, чтобы программа могла идентифицировать нашего бота Здесь нам нужно скопировать токен, чтобы программа могла идентифицировать нашего бота](https://habrastorage.org/getpro/habr/upload_files/2fb/e4d/ce6/2fbe4dce63a2779e697a65eca69343b0.png)
После этого, я создал проект .NET 5 в Visual Studio 2019. Используя NuGet, а именно, эти 4 библиотеки:
![](https://habrastorage.org/getpro/habr/upload_files/e85/b76/5e4/e85b765e423928dac6f7f8e9a74a0506.png)
Newtonsoft.Json
- является не обязательной, добавил исключительно для удобства. Можно использовать встроенную в .NET: using System.Text.Json;
Создал класс Brain, который будет управлять всеми жизненными основными процессами бота:
class Brain {
public static ITelegramBotClient Bot { get; set; } =
new TelegramBotClient("token"); // Заменить на свой токен
public static async Task HandleMessageAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
Console.WriteLine(JsonConvert.SerializeObject(update));
if (update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
{
var message = update.Message;
String queryString = "";
if (message.Text != null)
{
if (message.Text.ToLower() == "/start")
{
// Тут можно описать какую-то логику для приветствия, или произвести
// инициализацию бота, если такая требуется
await botClient.SendTextMessageAsync(message.Chat, "Добро пожаловать, полосатый");
return;
}
queryString = message.Text;// Если написать название песни текстом, вместо того,
// чтобы переслать ее. Это должно тоже работать. Поэтому мы отслеживаем текст,
// который присылают боту
}
if (message.Audio != null) //если это музыка, берем имя автора и песни
{
Audio audio = message.Audio;
queryString = audio.Performer;
queryString += " - " + audio.Title;
}
String url = "https://music.youtube.com/search?q=";
queryString = queryString.Replace(" ", "+");
Console.WriteLine(url += queryString);
await botClient.SendTextMessageAsync(message.Chat, url);
return;
}
}
public static async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
{
Console.WriteLine(JsonConvert.SerializeObject(exception));
}
}
Класс Program и непосредственно сам Main:
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Bot has been started: {Brain.Bot.GetMeAsync().Result.FirstName}");
var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
var receiverOptions = new ReceiverOptions
{
AllowedUpdates = { },
};
Brain.Bot.StartReceiving(
Brain.HandleMessageAsync,
Brain.HandleErrorAsync,
receiverOptions,
cancellationToken
);
Console.ReadLine();
}
}
Итак, на данный момент, наш бот уже умеет распознавать текст и готовые песни. После чего он берет их название, преобразовывает в query string, добавляет к стандартному url'у и возвращает готовую ссылку пользователю в ответ. Выглядит это примерно вот так:
![](https://habrastorage.org/getpro/habr/upload_files/1c2/f2c/4c5/1c2f2c4c536303da804467012239be0d.png)
Кстати, ему уже сейчас можно пересылать песни огромными пачками, в ответ от присылает столько же ссылок на них :)
Это не совсем то, что нам нужно, но мы уже ближе подобрались к решению.
Теперь нам нужно создавать плейлист, и загружать песни туда. Чтобы сделать это необходимо залогиниться в гугл, я буду использовать OAuth2. Прежде всего, я создал приложение в консоли: подключил необходимый инструмент для вызова апи YouTube(YouTube Data API v3) и настроил OAuth2 логин.
Описал класс YTMusicController:
class YTMusicController
{
private static String client_id = "your client id";
private static String client_secret = "your client secret";
internal static UserCredential credential;
private static YouTubeService youTubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApiKey = "your api key", // можно получить в приложении гугл консоли
HttpClientInitializer = Brain.credential,
ApplicationName = "YTMusic"
});
private static void Reinit()
{
youTubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApiKey = "AIzaSyDLk83ykqHtYOOjDwtvZvSf5S7wOIvGeGY",
HttpClientInitializer = credential,
ApplicationName = "YTMusic"
});
}
public static async Task Login() // функция которая позволяет залогинится с помощью OAuth2
{ // вызывается один раз при запуске бота
var client_secrets = new ClientSecrets();
client_secrets.ClientId = YTMusicController.client_id;
client_secrets.ClientSecret = YTMusicController.client_secret
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
client_secrets,
new[] { YouTubeService.Scope.Youtube },
"user",
CancellationToken.None,
new FileDataStore("YTMusicBrain")
);
Reinit(); // переприсваиваем объект YouTubeService с УЖЕ залогиненным юзером
}
public static async Task CreateNewPlaylist(Chat chatId)
{
Playlist playlist = new Playlist();
playlist.Snippet = new PlaylistSnippet();
var t = Guid.NewGuid().ToString(); // плейлист с случайным имене
playlist.Snippet.Title = t;
playlist.Snippet.Description = "This playlist created automatically";
playlist.Status = new PlaylistStatus();
playlist.Status.PrivacyStatus = "public";
playlist = await youTubeService.Playlists.Insert(playlist, "snippet, status").ExecuteAsync();
// после создания плейлиста я записываю его в мапу которая хранит значение
// плейлиста по значению чат
Brain.chat_playlist.Add(chatId.Id, playlist);
public static async Task<SearchResult> Search(String searchQuery)
{
var searchListResult = youTubeService.Search.List("snippet"); // формируем запрос для
searchListResult.Q = searchQuery; // для получения указанной песни
searchListResult.MaxResults = 1
var response = await searchListResult.ExecuteAsync();// отправляем запро
return response.Items[0];
public static async Task AddToTestPlaylist(Chat chatId, String searchQuery)
{
Playlist main = Brain.chat_playlist[chatId.Id];
var temp = new PlaylistItem()
var resultOfSearch = await Search(searchQuery); // получаем указанную песн
temp.Snippet = new PlaylistItemSnippet();
temp.Snippet.PlaylistId = main.Id;
temp.Snippet.ResourceId = new ResourceId();
temp.Snippet.ResourceId.Kind = "youtube#video";
temp.Snippet.ResourceId.VideoId = resultOfSearch.Id.VideoId;
// создаем новый плейлист, который помещаем в наш уже созданный
await youTubeService.PlaylistItems.Insert(temp, "snippet").ExecuteAsync();
return;
}
}
Добавил команду "/create" через упомянутого выше бота. После ее вызова у нас будет создаваться плейлист в YouTube. Дополним функцию HandleMessageAsync следующим кодом:
if (YTMusicController.credential == null)
{
await YTMusicController.Login();
}
//
...
.....
...
//
if (message.Text.ToLower() == "/create")
{
await YTMusicController.CreateNewPlaylist(message.Chat);
}
if ( chat_playlist != null && chat_playlist.ContainsKey(message.Chat.Id))
{
await YTMusicController.AddToTestPlaylist(message.Chat, queryString);
}
// если плейлист есть в списке, мы записываем в него найденную песню
// иначе мы просто возвращаем ссылку на песню
else
{
String url = "https://music.youtube.com/search?q=";
queryString = queryString.Replace(" ", "+");
Console.WriteLine(url += queryString);
await botClient.SendTextMessageAsync(message.Chat, url);
return;
}
Полный код класса Brain:
class Brain
{
public static ITelegramBotClient Bot { get; set; } = new TelegramBotClient("Api Key");
public static Dictionary<long, Playlist> chat_playlist = new Dictionary<long, Playlist>();
public static async Task HandleMessageAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
if (YTMusicController.credential == null)
await YTMusicController.Login();
Console.WriteLine(JsonConvert.SerializeObject(update));
if (update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
{
var message = update.Message;
String queryString = "";
if (message.Text != null)
{
if (message.Text.ToLower() == "/start")
{
await botClient.SendTextMessageAsync(message.Chat, "Добро пожаловать, полосатый");
return;
}
if (message.Text.ToLower() == "/create")
{
await YTMusicController.CreateNewPlaylist(message.Chat);
}
queryString = message.Text;
return;
}
if (message.Audio != null)
{
Audio audio = message.Audio;
queryString = audio.Performer;
queryString += " - " + audio.Title;
}
if ( chat_playlist != null && chat_playlist.ContainsKey(message.Chat.Id))
{
await YTMusicController.AddToTestPlaylist(message.Chat, queryString);
}
else
{
String url = "https://music.youtube.com/search?q=";
queryString = queryString.Replace(" ", "+");
Console.WriteLine(url += queryString);
await botClient.SendTextMessageAsync(message.Chat, url);
return;
}
}
}
public static async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
{
Console.WriteLine(JsonConvert.SerializeObject(exception));
}
}
Собственно, готово. Вот как это работает:
Единоразово при запуске бота, выполняется логин.
При вызове команды /create, создается новый плейлист, который записывается по ID чата в коллекцию(до вызова команды /create бот просто возвращает ссылку на песню).
Когда боту присылаешь песню, или название песни, он ищет ее в YouTube, возвращает, и там мы добавляем эту песню в наш плейлист.
Вообще, в идеале, было бы хранить данные плейлистов в какой-нибудь базе данных или на облаке, в крайнем случае, хотя бы записывать в файл(однако, я не заморачивался).
Проект исключительно "for fun", так что не судите строго:)