Сохраняем музыку на C#

image
В 2020ом мы пользуемся разными музыкальными сервисами, но как реликт ушедшей эпохи, в забытом профиле ВК, у многих хранится музыка. Функции для загрузки нет, но что если позарез нужно спасти аудиозапись?
Поскольку такого софта в открытом доступе не обнаружилось, кроме парочки веб-сервисов требующих авторизацию через ВК (что не очень то и безопасно), под катом мы рассмотрим процесс создания self-hosted утилиты на современном C# для загрузки своих аудио, не сливающей данные профиля сторонним сервисам.

Одной из ценностей работы программиста является простота и по возможности лаконичность кода. Поэтому мы склеим несколько уже существующих библиотек чтобы получить нужное решение.
Работать утилита будет так:
 dotnet vkm [login] [password] [audio-lemma]

Перво-наперво создадим репозиторий и опишем в одном файле csproj зависимости проекта
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <!--Утилита будет работать из консоли-->
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <!--Строго запрещаем null на этапе компиляции, чтобы застраховаться от NRE -->
        <Nullable>enable</Nullable>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
        <!--И включаем C# 9 который понадобится нам для top-level точки входа -->
        <LangVersion>9</LangVersion>
    </PropertyGroup>
    <ItemGroup>
        <!--Зависимость от VK API без необходимости вручную получать токен-->
        <PackageReference Include="VkNet" Version="1.56.0" />
        <!--Доступ к своим сообщениям, комментариям и музыке-->
        <PackageReference Include="VkNet.AudioBypassService" Version="1.7.0" />
    </ItemGroup>
</Project>

После этого с чистой совестью можно приступать к написанию кода. Нам потребуется авторизация утилиты в ВК с полным доступом к своему профилю. И как мы видим, благодаря экосистеме .NET, сделать это невероятно просто:
static class Vk
{
    internal static VkApi LoginToVkApi(string login, string password)
    {
        // Включаем доступ к своим сообщениям, комментариям и аудиозаписям
        var api = new VkApi(new ServiceCollection().AddAudioBypass());
        api.Authorize(new ApiAuthParams
        { 
            ApplicationId = 1980660,
            Login = login, 
            Password = password, 
            Settings = All 
        });
        $"Login as vk.com/id{api.UserId}".Println(DarkBlue);
        return api;
    }
}

Опишем точку входа и фильтр загружаемых аудиозаписей. Используем для этого top-level programs и прямо в файле Application.cs валидируем аргументы, одновременно инициализируя api
var vk = args.Length switch
{
    3 => LoginToVkApi(args[0], args[1]),
    _ => throw new ArgumentException("Invalid arguments. Usage:\n" +
        "  dotnet vkm [login] [password] [audio]\n" +
    )
};

Приводим лемму для поиска аудиозаписи к upper-case
var lemma = args.Last().ToUpperInvariant();

И грепаем с помощью Linq все аудиозаписи с её вхождением. Отдельное спасибо хабраюзеру SuperHackerVk за способ получения mp3-ссылки регуляркой.
var audios = vk.Audio.Get(new AudioGetParams { Count = 6000 })
    .Where(x => x.Title.ToUpperInvariant().Contains(lemma))
    .Select(x => (x.Title, Url: Regex.Replace(
        x.Url.ToString(),
        @"/[a-zA-Z\d]{6,}(/.*?[a-zA-Z\d]+?)/index.m3u8()",
        @"$1$2.mp3"
    )));

Наконец остается только загрузить свои найденные аудио:
using var http = new HttpClient();
foreach (var (title, url) in audios)
{
    $"Downloading {title}...".Println(DarkBlue);
    await WriteAllBytesAsync($"{title}.mp3", await http.GetByteArrayAsync(url));
}

Вот и все! Утилита написана и готова к использованию в личных целях. Заметно как C# с каждым годом все больше превращается в хороший мультитул, позволяющий решать любой спектр задач. Расширения синтаксических возможностей которые при анонсах кажутся загромождающими язык, на практике напротив, позволяют сократить код и сделать его простым и понятным.

Репозиторий на GitHub c небольшими дополнениями и документацией по запуску.
Всем удачного дня!

Комментарии 38

    +7
    То есть вы просто показали как установить два пакета из нугет и воспользоваться парой их функций?
      +2
      Ну что вы. Автор ещё включил Nullable ref types, которой ни разу не воспользовался. И если использовалась превью версия языка, то почему бы не воспользоваться превью .Net 5. Да, это в этом микро проекте ничего не даст, но раз уж автор начал.
        +1

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

          –1
          NRT в .Net не дают гарантий того, что вы не получите NRE (NPE в java и может ещё где), т.к.:
          — анализатор не все ситуации может отработать корректно
          — разработчик может подавить анализ использовав!
          — разработчик библиотеки, которую вы используете не факт, что тоже включил их поддержку и не факт, что всё разметил правильно
          В итоге у вас вагон и маленькая тележка способов обратиться по null. Включать фичу в таком мелком проекте имеет смысл только для того, чтобы потренироваться в её использовании, но мы не видим её применения НИ РАЗУ. А заострять на этом внимание в статье, если она не используется, вообще не имеет никакого смысла.
          p.s. Я не против конкретно этой фичи, я даже за, но я против того, что автор указывает на фичу без её использования.
          Строго запрещаем null на этапе компиляции, чтобы застраховаться от NRE

            +1

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

              +1
              Включать фичу в таком мелком проекте имеет смысл только для того, чтобы потренироваться в её использовании, но мы не видим её применения НИ РАЗУ.

              И какого использования NRT вы ожидали? Ошибок компиляции? Смысл ведь как раз в том чтобы изначально не давать программисту писать код использующий null. На мой взгляд не лишнее ограничение свободы.


              разработчик может подавить анализ использовав!

              Вы всерьез записали это в минусы NRT? Точно также происходит и в остальных языках с декларируемой null-safety (в Kotlin '!!' например). В этом и смысл, что использование null сразу будет заметно в коде.

                0
                Это были не минусы NRT, это были аргументы к тому, что NRT не даёт 100% гарантии.
                Ограничения статической компиляции… и получить NPE в рантайме.

                И да, это не является ограничением статической компиляции, это лишь анализ проекта с возможностью поднять предупреждения до уровня ошибк.
          +2
          Аналогичной self-hosted утилиты нет в открытом доступе. Есть парочка веб-сервисов которые требуют входа через профиль ВК, что не очень то безопасно, а youtube-dl не умеет нормально выкачивать аудио из ВК. Поэтому статья описывает несуществующее доселе решение. Не в этом ли и заключается ценность софта?
          Делать сложные вещи по возможности просто и лаконично одна из задач работы программиста. Или вы хотели полотнище кода для статьи на хабре?
            –2
            требуют входа через профиль ВК, что не очень то безопасно

            А в чём их небезопасность? Просят вводить логин/пасс? Тогда да, доверять не стоит. А если авторизация через приложение ВК, то что в этом такого?
              +3
              Доступ к профилю автоматически дает сервису данные страницы, электронную почту, друзей, фотографиям. Не давая при этом логина и пароля.
              Как-то не хочется чтобы эти данные собирались
                –1
                Ах перестаньте )) Эти данные давно собраны теми кому надо ) И без вашего согласия.
                  +4

                  Вы хотите чтобы вдобавок к "кому надо", эти данные получил еще и какой нибудь сервис Васи Пупкина?)

                  0
                  Логин и пароль не дает доступ к этой и другой информации?
            +1

            Лучше б в youtube-dl поддержку vk добавили, чем ещё одну утилиту плодить.

              +2
              Было бы куда отправлять pull-request :)
              Да и C# как язык нравится мне больше Python для реализации своих идей.
                –2

                Youtube-dl же, вроде, на гитхабе лежит?
                А опенсорс — он такой, дикий, пишешь на том, на чём народ угораздило проект создать, а не на том, на чём хочется.

                  +2
                  Так в том и дело что уже не лежит: habr.com/ru/news/t/524858
                  Собственно, этой новостью статья и навеяна, как некий челлендж.
                    0

                    А. Ну переедут, делов-то. Не первый и не последний раз удаляют подобный софт.

                      0

                      Жаль лишь стертых issues и пул-реквестов, как и мне, в своё время.

                0
                Она там вроде уже давно есть, если не отвалилась.
                  +1
                  Она там использует склейку кусочков аудио по ссылке на m3u8, причем не очень корректный файл на выходе получается.
                0

                дано: есть группа в вк (или страничка посвященная определеной теме, или открытый аккаунт пользователя), в которой контент добавлялся уже больше 10 лет (т.е. контента много). нужно все что есть скопировать в максимально доступном качестве в удобочитаемом виде. т.е. все записи с коментами, все видео с коментами, все фотоальбомы со всеми фото с коментами, всю музыку с коментами. если нужно, то можно зарегистрировать левый аккаунт. желательно все это сделать через консольную утилиту, которой в качестве входящих параметров подать только ссылку на группу или акаунт который нужно сграбить. ну максимум логин\пароль от толькочто зарегистрированого одноразового аккаунта вк.утилита автоматически должна все сделать сама. в фотоальбомах нужно учесть порядок фото, в музыку и видео добавить дату загрузки и количество просмотров. есть какие идеи чем это можно сделать? youtube-dl к сожалению не умеет с вк работать.

                  +1
                  Просто пройтись селениумом сохраняя все в HTML, на выходе получите локальную копию всех страниц.
                    0
                    Селениумом? Сохранить все HTML можно и без него =\ Это как из пушки по воробьям.
                      +1
                      Если пушка и ядра дешевые, то почему бы и нет
                    0

                    Готовым нет, только если сделать на заказ.

                    0
                    так ведь были же плагины для браузеров — появляется кнопочка в вк, нажал ее и скачал весь плэйлист.
                      +3

                      А есть реально рабочие сейчас? все сломались давно.и на новых версиях браузерв и на старых.только приложение "VMP" под анроид спасает

                        0
                        посмотрел сейчас. у меня стоит VKD — но качает только по одной композиции, кнопки «Скачать все» нет(
                        0

                        Есть savefrom.net как плагин для браузеров и как веб-сервис, есть ss.youtube.com префикс для скачивания с Ютуба. И без костылей и без авторизации.

                        +1
                        1. Не знаю, я как использовал связку VK и скачанных оттуда же mp3-шек в оффлайне, так и использую, так что про «реликт ушедшей эпохи» момент как минимум спорный.
                        2. Ещё бы проставлять исполнителя, альбом и название из ВК по-умолчанию. А то обычно они либо вообще не проставлены, либо проставлены как-то криво при такой загрузке.
                        3. Ну и на самом деле, скачать всё сразу не особо удобно, я обычно кусками закачиваю с неким периодом. Соответственно, эта конкретная прога без доработок подойдёт скорее для единоразового бэкапа.

                        П.С. Сам до сих пор продолжаю пользоваться богомерзким сливающим данные SaveFromNet-швайном
                          0
                          Ещё бы проставлять исполнителя, альбом и название из ВК по-умолчанию.

                          Готово
                            0
                            Ну и на самом деле, скачать всё сразу не особо удобно, я обычно кусками закачиваю с неким периодом.

                            Добавил загрузку всех аудио разом.
                            0
                            Используется недокументированный хак, и он не всегда работает.

                            Например, API выдаёт такой URL плейлиста, тут данные есть.

                            Регекспом он конвертится в такой URL MP3, тут уже 404.

                            Заметил, что если записи размещены на сервере https://cs***.vkuseraudio.net/* то программа скачивает, а если на сервере https://psv4.vkuseraudio.net/* (а таких у меня большинство) — не скачивает.
                              0
                              Занес в TODO, планируется исправление.
                              0
                              Может, совпадение, может, нет, но при использовании программы при следующем входе в vk он ругнулся на подозрительную активность и мол, давай верифицируемся через телефон.

                              100 лет уже телефон не спрашивал, заходил по паролю, и тут такое.
                                +1
                                Автор, расскажите такую вещь.
                                Я видел несколько проектов по скачиванию с ВК и везде одни и те же костыли. Зачем получать mp3 ссылку, используя старую функцию vkapi (audio.getById), которую в любом момент заблокируют? Regexp, представленный в той статье (и в вашем коде), работает далеко не со всеми треками.
                                Почему нельзя просто взять и скормить hls ffmpeg'у? Он на выходе даст скаченный mp3… А еще можно вывести получившийся mp3 в — (то есть в stdout) и юзнув stdout redirection получить byte[] с mp3, с которым также можно сделать что угодно (отправить куда-то например)
                                  +1
                                  Спасибо за идею. Отсыпал вам кармы

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое