Загрузка музыки из vk.com на компьютер используя C#

Сегодня будет достаточно длинная, но полезная статья о том, как реализовать загрузчик музыки из vk.com (вконтакте) на C#. Зачем это нужно, ведь существует огромное количество сервисов, которые могут это сделать? Как минимум, уметь работать с API VK это хорошо, а уметь делать это на C# — еще лучше). Да и использовать свой «велосипед» порой намного приятнее…

Для начала сформулируем требования, для нашего приложения. Приложение должно уметь авторизоваться под нашей учётной записью, получать список мелодий у заданного пользователя либо группы, производить фильтрацию списка для поиска требуемой мелодии, возможность прослушать мелодию до ей загрузки, возможность загрузки отдельных мелодий или всего списка сразу в заданный пользователем каталог.

Начнем по порядку. Для того, чтобы приложение имело возможность работать с API «вконтекте» оно должно получить соответствующие разрешения. Чтобы это сделать, необходимо создать новое приложения на сайте vk.com. Для этого зайдём в раздел «разработчиками» (http://vk.com/dev) и выберем «Создать приложение». Записываем название приложения, выбираем тип «Standalone-приложение» и нажимаем «Подключить приложение».

После этого, придётся подтвердить код по СМС, а после его подтверждения мы увидим страницу для заполнения информации о приложении. Внеся требуемые изменения (при желании) необходимо нажать на «Сохранить изменения», после чего перейти на вкладку «Настройки», на которой можно увидеть ID нашего приложения.

Данный ID следуем записать (или запомнить), так как в дальнейшем он нам понадобится.

Теперь время переходить к созданию самого приложения. Для этого создаём новый проект типа «Wpf Application». Для удобства, сразу набросаем простенький интерфейс, выглядеть это будет примерно так.

Также добавим на форму элемент типа «WebBrowser», который будет занимать всё пространство на форме, но отключим его отображения выбрав параметр Visibility="Hidden". Элемент WebBrowser будет использоваться для авторизации на сайте Vk.

Теперь зайдём в файл App.config и в раздел appSettings вставим 3 строки.

    <add key="VKAppId" value="4742982" />
    <add key="VKScope" value="8" />
    <add key="VKRedirectUri" value="http://oauth.vk.com/blank.html" />
  • VKAppId — ID приложения, созданного на первом этапе.
  • VKScope — права, которые требуются приложению, для доступа к музыке будет достаточно указать «8». Более подробно можно узнать здесь: http://vk.com/dev/permissions
  • VKRedirectUri — адрес, на который будет передан access_token.

Теперь зайдём в файл MainWindow.xaml.cs в нём найдём конструктор MainWindow() и добавим в его строки:

webBrowser.Visibility = Visibility.Visible;
            webBrowser.Navigate(String.Format("https://oauth.vk.com/authorize?client_id={0}&scope={1}&redirect_uri={2}&display=page&response_type=token", ConfigurationSettings.AppSettings["VKAppId"], ConfigurationSettings.AppSettings["VKScope"], ConfigurationSettings.AppSettings["VKRedirectUri"]));  

webBrowser — это имя ранее добавленного компонента WebBrowser. Visibility.Visible делает его видимым, сам элемент перерывает остальные элементы и отображает страницу, переданную в методе Navigate.

Теперь создадим класс Vk следующего вида:

public static class Vk
{
    public static string AccessToken { get; set; }
    public static string UserId { get; set; }
} 

Использование статического класса позволит получить глобальный доступ к переменным из любой части приложения.

  • AccessToken — хранит токен доступа, который мы получим после авторзации;
  • UserId — идентификатор пользователя, под которым авторизовались.

Теперь для элемента webBrowser добавим обработку события Navigated, которое срабатывает после загрузки страницы.

private void WebBrowserNavigated(object sender, NavigationEventArgs e)
{
    var clearUriFragment = e.Uri.Fragment.Replace("#", "").Trim();
    var parameters = HttpUtility.ParseQueryString(clearUriFragment);
    Vk.AccessToken = parameters.Get("access_token");
    Vk.UserId = parameters.Get("user_id");
    if (Vk.AccessToken != null && Vk.UserId != null)
    {
        webBrowser.Visibility = Visibility.Hidden;
    }
}

В первой строке происходит удаление символа «#», который может помешать дальнейшей обработке строки.

HttpUtility.ParseQueryString(clearUriFragment); — разбивает строку на фрагменты. HttpUtility находится здесь: System.Web.HttpUtility

В следующих двух строчках происходит попытка получения токена доступа и идентификатора авторизованного пользователя.

Если требуемые данные получены, то происходит сокрытие элемента WebBrowser.

При авторизации, могут появляться разрешающие сообщения, это связато с тем, что WebBrowser работает через установленный Internet Explorer.

Сейчас, если мы запустим приложение и введём данные для входа, то увидим каркас приложения, без каких-либо обработчиков.

Теперь нужно создать классы, для работы с пользователями, группами и пользователями Vk. Зайдём на страницу описания API Vk http://vk.com/page-1_2369282. Нам понадобятся запросы users.get, groups.getById, audio.get. Заходим в каждый из этих методов и копируем пример ответа в формате JSON.

Для user.get пример будет следующим

{"response":[{"uid":"1","first_name":"Павел","last_name":"Дуров",
"photo":"http:\/\/cs109.vkontakte.ru\/u00001\/c_df2abf56.jpg"},
{"uid":"6492","first_name":"Andrew","last_name":"Rogozov",
"photo":"http:\/\/cs537.vkontakte.ru\/u06492\/c_28629f1d.jpg"}]}

Теперь создаём новый класс VkUsers. Заходим в его, удаляем сам класс и делаем следующее, выбираем EDIT Paste SpecialPaste JSON As Classes. Это позволит автоматически создать класс на основе примера JSON ответа, останется только переименовать полученные классы. В итоге получаем классы следующего содержания.

public class VkUsers
{
    public UserResponse[] response { get; set; }
}

public class UserResponse
{
    public string uid { get; set; }
    public string first_name { get; set; }
    public string last_name { get; set; }
    public string photo { get; set; }
}

Для работы с группами и альбомами поступаем аналогичным образом.

Теперь добавляем в класс MainWindow 2 элемента, для хранения всех музыкальных композиций, и плеер для прослушивания нужной композиции.

public AlbumResponse[] AllComposition { get; private set; }
private readonly MediaPlayer _player = new MediaPlayer();

Теперь добавим функции, осуществляющие загрузки альбомов пользователя либо группы. Функция загрузки альбомов пользователя будет выглядеть следующим образом.

private void LoadUserCompositions(string userId)
{
    var str = string.Format("https://api.vk.com/method/users.get?uids={0}",
    userId);
    var responseText = VkRequest(str);
    try
    {
        var users = JsonConvert.DeserializeObject<VkUsers>(responseText);
        var uid = users.response[0].uid;
        str = string.Format("https://api.vk.com/method/audio.get?uid={0}&access_token={1}",
            uid, Vk.AccessToken);
        responseText = VkRequest(str);
        var album = JsonConvert.DeserializeObject<VkAlbum>(responseText);
        AllComposition = album.response;
        albumCompositions.ItemsSource = album.response;
        Title = String.Format("Музыка пользователя: <{0} {1}> загружена", users.response[0].first_name,
        users.response[0].last_name);
    }
    catch (Exception) { }
}

Загрузка альбомов из группы будет аналогичной, её можно увидеть в исходниках приложения.

Теперь перейдём к обработчику кнопки «Загрузить список композиций». Его код будет следующим
if (userName.Text == "" && groupName.Text == "")
{
    LoadUserCompositions(Vk.UserId);
    return;
}
if (userName.Text != "")
{
    LoadUserCompositions(userName.Text);
    return;
}
if (groupName.Text != "")
{
    LoadGroupCompositions(groupName.Text);
}

Теперь добавим воспроизведение выделенной композиции при нажатии правой клавиши мыши. Добавим обработчик события щелчка правой кнопки мыши на элемент типа «DataGrid». Со следующим кодом.

var selected = (AlbumResponse)albumCompositions.SelectedItem;
if (selected != null)
{
    _player.Open(new Uri(selected.url, UriKind.RelativeOrAbsolute));
    _player.Play();
    compositionName.Text = String.Format("Играет: {0}", selected.title);
    stopPlayer.IsEnabled = true;
}
При нажатии кнопки «Остановить» необходимо выполнить _player.Stop() и сделать кнопку недоступной для нажатия.

Следующим шагом будет добавление фильтра выводимых песен. Для этого на поле фильтра вешаем событие изменения содержимого с кодом как в примере:

var str = filter.Text.ToLower();
if (AllComposition != null && AllComposition.Any())
    {
        if (String.IsNullOrEmpty(str))
        {
            albumCompositions.ItemsSource = AllComposition;
            return;
        }
        albumCompositions.ItemsSource =
            AllComposition.Where(
                x =>
                    x.artist.ToLower().IndexOf(str, StringComparison.Ordinal) >= 0 ||
                    x.title.ToLower().IndexOf(str, StringComparison.Ordinal) > 0);
    }
Оно позволит отобразить только те композиции, название которых, либо автор содержат введённый текст.
Осталось реализовать загрузку композиций. Функция загрузки композиции будет выглядеть следующим образом.
private async Task DownloadCompositions(AlbumResponse composition, String path)
{
    try
    {
        if (path[path.Length - 1] != '\\')
        {
            path = path + "\\";
        }
        var fileName = composition.title;
        if (fileName.Length > 40)
        {
            fileName = fileName.Substring(0, 40);
        }
        fileName = fileName.Replace(":", "").Replace("\\", "").Replace("/", "").Replace("*", "").Replace("?", "").Replace("\"", "");
        using (var client = new WebClient())
        {
            client.DownloadProgressChanged += (o, args) =>
                {
                    progressBar.Value = args.ProgressPercentage;
                };
            client.DownloadFileCompleted += (o, args) =>
                {
                    progressBar.Value = 0;
                    compositionName.Text = "";
                };
            compositionName.Text = composition.title;
            await client.DownloadFileTaskAsync(new Uri(composition.url), path + fileName + ".mp3");
        }
    }
    catch (Exception)
    {
    }
}

В параметрах передаются композиция для загрузки и путь для сохранения (каталог). Далее проверяем, содержит ли каталог на конце символ «\», если его нет, то добавляем, это необходимо для формирования корректного пути сохранения композиции. Дальше очищаем название композиции от не валидных символов. Теперь создаём WebClient который будет отвечать за загрузку композиции. Имя композиции делаем не больше 40 символов (если кому-то нравятся длинные файлы, то эту операцию можно вообще убрать). В событии DownloadProgressChanged описываем изменение значения прогресс бара. В событии DownloadFileCompleted действия, которые необходимо выполнить после загрузки композиции. DownloadFileTaskAsync позволяет загружать требуемый файл в асинхронном режиме, для того чтобы функция дождалась завершения загрузки, необходимо прописать await, а саму функцию пометить как async.

Код, который будет скачивать все выделенные композиции будет выглядеть следующим образом.
var selectedBuf = albumCompositions.SelectedItems;
var selected = selectedBuf.Cast<albumresponse>().ToList();
if (selected.Count > 0)
{
    var dialog = new CommonOpenFileDialog {IsFolderPicker = true};
    var result = dialog.ShowDialog();
    if (result == CommonFileDialogResult.Ok)
    {
        var path = dialog.FileName;
        foreach (AlbumResponse composition in selected)
        {
            await DownloadCompositions(composition, path);
        }
    }
}

Как можно увидеть, выделенные элементы копируются в новую переменную. Это необходимо для того, чтобы не возникало проблем с повторным выделением при сохранении нескольких композиций. Чтоб�� работал диалог выбора каталога для сохранения, необходимо добавить пакет «WindowsAPICodePack-Shell» через NuGet. Если каталог был выбран, то происходит скачивание всех выбранных композиций в указанный каталог.

Спасибо всем, кто смог дочитать до конца. Исходный код приложения можно увидеть в репозитории GitHub: https://github.com/flash2048/VkMusicDownload.

При желании программа легко может быть дополнена такими возможностями как:

  • Поиск песен по ключевым словам среди всех композиций Vk.
  • Скачивания вашей музыки, музыки друзей и друзей друзей без повторений.
  • При наличии огромного желания и ресурсов можно попробовать сказать все композиции вконтакте, разложив их на какие-то условные группы.
При наличии нескольких часов свободного времени обязательно попробуйте поработать с API Vk, оно оказалось на редкость дружелюбным и понятным…

Если появились какие-то вопросы — не стесняйтесь писать их в комментариях либо личными сообщениями.

Приятного программирования…

Дополнение:
Исправлена ошибка - WebBrouser переименован на WebBrowser
Добавлена возможность перематывания мелодии.

P.S.
Целью данной статьи не было создание плеера. Статья носит исключительно обучающий характер.

Если в Вас есть проблемы с запуском кода, можете написать мне, мы свяжемся в Skype и попробуем разобраться.

Комментарии (37) -

Никита 29.01.2015 21:49:33

В кои-то веке полезный урок на эту тему.  К сожалению, хоть в программировании я и не совсем новичек, но в интернет интерфейсах понимаю не так много. Попытался для начала разобраться в исходнике, но столкнулся с проблемой, что приложение не уходит дальше страницы получения токена (не появляется форма для скачивания вместо этого отображается текст "Пожалуйста, не копируйте данные из адресной строки для сторонних сайтов. Таким образом Вы можете потерять доступ к Вашему аккаунту").  Можете помочь разобраться в вопросе?

Андрей 02.02.2015 9:45:09

Если появляется данное сообщение, то токен был получен. Если основной интерфейс не появляется, то неверно срабатывает метод «WebBrouserNavigated». Я бы посоветовал подебажить его. Еще можно добавить кнопку, принудительно скрывающую компонент «webBrouser». Если не получится разобраться, свяжитесь со мной — будем думать дальше…

Никита 05.02.2015 19:21:08

Просто скрыть форму будет недостаточно. Отладка показывает, что неправильн отрабатывает элемент WebBrouserNavigated.  А все из-за того, что метод не может получить Vk.AccessToken и Vk.UserId, так как пытается пропарсить строку "https://oauth.vk.com/blank.html"; вместно строки с токеном.

Андрей 06.02.2015 13:18:00

Если хотите, можем связаться по скайпу и разобраться, в чем проблема...

8Observer8 22.01.2016 14:58:42

Хорошая и единственная статья по WPF и vk, что я нашёл.

Я скачал проект с github, заменил VkAppId на свой Id. На всякий случай, в настройках поставил "Приложение включено и видно всем". Но при запуске дело не движется дальше сообщения ""Пожалуйста, не копируйте данные из адресной строки для сторонних сайтов."

Попробую начать с начала. Выведу список друзей. Сделаю, чтобы появлялось окно LoginWindow, а потом основное окно.

Замечу, что ConfigurationSettings.AppSettings подчёркивается зелёным и пишет, что этот метод типа был заменён на что-то там другое.

8Observer8 22.01.2016 15:49:54

Вместо ConfigurationSettings.AppSettings["VKAppId"] нужно использовать ConfigurationManager.AppSettings["VKAppId"]

Нужно добавить using System.Configuration; (и не забыть добавить System.Configuration в References)

8Observer8 22.01.2016 16:38:32

Вы не могли бы мне помочь? Почему у меня Fragment пустой?

        public LoginWindow()
        {
            InitializeComponent();

            webBrowser.Navigate("oauth.vk.com/authorize://oauth.vk.com/blank.html&display=page&scope=8&response_type=token");
        }

        private void WebBrouserNavigated(object sender, NavigationEventArgs e)
        {
            MessageBox.Show(e.Uri.Fragment.ToString());
        }

Никита 22.01.2016 23:32:13

Если разберетесь с ошибками - залейте куда-нибудь исправленные исходники, пожалуйста Smile

8Observer8 22.01.2016 16:41:12

Предыдущий код скопировался на блог с ошибками, поэтому ещё раз: http://paste.ubuntu.com/14597595/

8Observer8 22.01.2016 18:22:36

Забыл написать, что у меня выскакивает сообщение: www.mediafire.com/.../vkErrorScript.png

8Observer8 22.01.2016 23:53:47

Проблема решена. Мне подсказали, что это из-за старого Internet Explorer'а. Я на Win7 его ни разу не обновлял. Правда, сообщение "Script Error" всё равно появляется, но это для меня сейчас ерунда.

Никита 23.01.2016 21:02:03

Забавно. Скачал сейчас исходник, ввел данные, внезапно все заработало "из коробки" Smile

8Observer8 23.01.2016 1:25:05

Я думаю, что нелишним было бы добавить вывод на экран сообщения: Update your IE Browser. Я думаю, у Никиты такая же была проблема - нужно было обновить IE

        private void WebBrouserNavigated(object sender, NavigationEventArgs e)
        {
            var clearUriFragment = e.Uri.Fragment.Replace("#", "").Trim();
            var parameters = HttpUtility.ParseQueryString(clearUriFragment);
            Vk.AccessToken = parameters.Get("access_token");
            Vk.UserId = parameters.Get("user_id");
            if (Vk.AccessToken != null && Vk.UserId != null)
            {
                webBrouser.Visibility = Visibility.Hidden;
            }
            else
            {
                MessageBox.Show("AccessToken is null. Update your IE Browser");
            }
        }

Благодарю автора за статью.

Возникла небольшая проблема при запуске приложения: аудио не воспроизводится, вылетает с ошибкой "An unhandled exception of type 'System.NullReferenceException' occurred in PresentationCore.dll Additional information: Ссылка на объект не указывает на экземпляр объекта." в методе AlbumCompositionsMouseRightPlayComposition, строка  Player.Open(new Uri(selected.url, UriKind.RelativeOrAbsolute));

Кто-нибудь сталкивался?

Спасибо автору за статью!
Если будет время и желание, пожалуйста дополните приложение возможностью перематывать песню с помощью ползунка и чтобы композиции воспроизводились один за другим. Спасибо.

Андрей 04.05.2016 18:01:17

Всем привет! Не ожидал, что статья вызовет интерес, мне очень приятно.  В эти выходные постараюсь найти время исправить найденные проблемы и дополнить функционал в соответствии с запросами.

Я до конца так и не добрался. Буду следить за новостями и пытаться дочитать и доразбирать код. С нетерпением жду устранение проблем и расширения функционала Smile Спасибо вам за очень интересную статью!

Алексей 04.05.2016 23:17:51

Можно же использовать библиотеку C# для вк апи, тогда сразу отпадает необходимость использования webrouser  с его косяками

Андрей 05.05.2016 8:47:24

У данной статьи обучающий характер. Понятно, что почти всегда можно использовать что-то готовое. Но порой своих "волосипедов", все же, не хватает.

Артем 05.05.2016 10:01:02

За статью спасибо.
"WebBrouser" - глаз режет, что за бро юзер Smile
Для статьи пройдет еще, но раз уже на гитхаб вылил, то, думаю, стоит исправить.

Андрей 05.05.2016 10:03:03

Спасибо за замечания. В выходные исправлю.

Никита 05.05.2016 12:07:44

Я рукожоп, например. Долго бился, но так и не смог прикрутить библиотеки C# (я не проггер, интересуюсь этим поскольку-постольку), а с помощью этой статьи удалось вникнуть хотя бы примерно как это все дело работает)

Кроме самой работы с Vk API в статье ещё очень много таких вещей, которые я не знал как применять на практике. А по поводу библиотек, то нужно знать как работать без них, а потом открыть исходники библиотек (они все открытые) посмотреть как там реализовано, сравнить, дописать, если чего-то не хватает, применять знания оттуда и оттуда в других проектах. Тем более у меня как у начинающего не хватило мозгов понять как работать с библиотеками для Vk. А по этой статье я многому научился

В функции воспроизведения песни, там где идет _player.Play(new Uri(...)) у меня почему то ошибку выдает:
"Необработанное исключение типа "System.NullReferenceException" в PresentationCore.dll
Дополнительные сведения: Ссылка на объект не указывает на экземпляр объекта."
На мсдн вроде точно так же делается в примере. Плеер создается. В чем ошибка? Не могу понять какой объект null...

Кирилл 09.05.2016 9:06:54

Спасибо за статью!
Такой вопрос,  существуют ли такие апи вк, которые позволяли бы скачивать документы из обсуждений ? Т.е. есть к примеру веточка, где много народа выкладывает различные файлы (все экселевские) можно ли их программным путем скачивать?

Андрей 09.05.2016 14:46:22

Здравствуйте.
Можно. Рекомендую ознакомиться с существующим API: https://vk.com/dev/methods
Конкретно, нужны методы для Documents

Андрей 09.05.2016 14:50:11

Я обновил статью.
Исправил ошибку неверной записи WebBrowser, а также добавил возможность проматывать текущую мелодию.
На счет конкретных ошибок, возникающих у некоторых, конкретно не могу сказать, так как причин может быть много. Можете связаться со мной по скайпу, попробуем разобраться.

Доброго времени суток.Такой вопрос, у меня после запуска появляется ошибка "Не удалось скачать authorize из oauth.vk.com. Не удалось открыть этот веб-сайт и т.д.". В чем может быть проблема? ID приложения поменял на свой.

Андрей 17.05.2016 16:53:23

Здравствуйте. Если проблему решить не удаётся, свяжитесь со мной по скайпу, попробуем разобраться вместе.

Спасибо, справился).

Уважаемый Андрей, здравствуйте! Подскажите, каким образом можно обработать JSON запрос, если перед его элементами идет  количество элементов. Например, при запросе видео в ВК. Если сформировать класс на основе примера Json запроса я смог (удалив количество в блокноте и скопировав запрос без него), то ответ то все равно приходит с количеством и  соответственно не получается закинуть в DataGrid массив объектов видео

Андрей 23.05.2016 14:33:43

Здравствуйте. Напишите, пожалуйста, пример JSON который вам нужно десериализовать, постараюсь помочь.

Спасибо большое, уже сам разобрался) Пришлось в ответе удалять количество, тогда нормально все работает.

Павел 16.09.2016 12:09:55

Здравствуйте!
В методе DownloadCompositions, строка 27 (с await):
path + fileName лучше заменить на Path.Combine(path,fileName) из System.IO.

Информация: msdn.microsoft.com/.../fyy7a5kt(v=vs.110).aspx

Тогда можно будет убрать костыль из строк 5-8.

Андрей 19.10.2016 7:17:03

Да, это так, спасибо.

Дмитрий 12.10.2016 20:02:04

Урок просто супер !. Он показал все необходимое для работы с вк. А самое главное меня удивило JSON огого как просто создать class я в C# ровно неделю и уже могу с вк вытворять такие проделки, конечно я долго мучился и пытался понять как это все оттуда извлекается ) Спасибо за урок! Всегда было интересно как это работает и вот можно сказать я уже знаю!) В интернете нет ничего подобного, а если и есть то уже совсем старое!)

сейчас  запустил программу, почемуто не работает. наверно потому что они теперь используют какоето шифрование?

Добавить комментарий