Skip to content
inyutin-maxim edited this page Feb 10, 2020 · 64 revisions

FAQ - Часто задаваемые вопросы



Для обновления на версию 1.30.2 - 1.32.0 необходимо сначала установить предварительную версию NLog 4.5.0-rc05 или выше. С версии 1.33.0 Установка происходит как обычно


Что делать при возникновении ошибки CaptchaNeededException?

  1. Реализовать интерфейс ICapthaSolver (пример реализации интерфейса) и пробросить реализацию в конструктор:
var api = new VkApi(LogManager.CreateNullLogger(), new CptchCaptchaSolver());

Как отправить простое сообщение?

Api.Messages.Send(new MessagesSendParams
{
    UserId = 12345678, //Id получателя
    Message = "Message", //Сообщение
    RandomId = new Random().Next(999999) //ужасный уникальный идентификатор
});

Как сформировать вложение для сообщения из файлов ВК?

Ниже представлен пример, как можно прикрепить к сообщению файл, который уже загружен на сервера ВК:

var albumid = 123456789;
var photos = Api.Photo.Get(new PhotoGetParams
{
	AlbumId = PhotoAlbumType.Id(albumid),
	OwnerId = Api.UserId.Value
});
Api.Messages.Send(new MessagesSendParams
{
	Attachments = photos,
	Message = "Message",
	PeerId = Api.UserId.Value
});

Как сформировать вложение для сообщения из локальных файлов или ссылок?

Данный способ позволяет прикрепить к сообщению локальный файлы, а также файлы, взятые из интернета, то есть прикрепление файла через ссылку.
Если нужно отправить картинку, то для прикрепления её к сообщению можно использовать следующий метод:

public async void SendMessageWithImage(this VkApi Api)
{
    var userId = 12345678; //Получатель сообщения

    // Получить адрес сервера для загрузки картинок в сообщении
    var uploadServer = Api.Photo.GetMessagesUploadServer(userId);

    // Загрузить картинку на сервер VK.
    var response = await UploadFile(uploadServer.UploadUrl, 
        "https://www.gstatic.com/webp/gallery/1.jpg", "jpg");

    // Сохранить загруженный файл
    var attachment = Api.Photo.SaveMessagesPhoto(response);

    //Отправить сообщение с нашим вложением
    Api.Messages.Send(new MessagesSendParams
    {
        UserId = userId, //Id получателя
        Message = "Message", //Сообщение
        Attachments = attachment, //Вложение
        RandomId = new Random().Next(999999) //Уникальный идентификатор
    });
}

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

public async void SendMessageWithFile(this VkApi Api)
{
    var userId = 12345678; //Получатель сообщения

    // Получить адрес сервера для загрузки файлов в сообщении
    var uploadServer = Api.Docs.GetMessagesUploadServer(userId);

    // Загрузить файл на сервер VK.
    var response = await UploadFile(uploadServer.UploadUrl,
        "https://i.gifer.com/D446.gif", "gif");

    // Сохранить загруженный файл
    var title = "Test Gif"; //Название файла
    var attachment = new List<MediaAttachment>
    {
        Api.Docs.Save(response, title ?? Guid.NewGuid().ToString())[0].Instance
    };

    //Отправить сообщение с нашим вложением
    Api.Messages.Send(new MessagesSendParams
    {
        UserId = userId, //Id получателя
        Message = "Message", //Сообщение
        Attachments = attachment, //Вложение
        RandomId = new Random().Next(999999) //Уникальный идентификатор
    });
}

Данный метод является универсальным для любых типов файлов.
Если файл взят из интернета то в метод UploadFile в качестве аргумента file мы передаем ссылку на этот файл.
Если мы загружаем локальный файл, то в аргумент file мы передаем путь к этому файлу.

private async Task<string> UploadFile(string serverUrl, string file, string fileExtension)
{
    // Получение массива байтов из файла
    var data = GetBytes(file);

    // Создание запроса на загрузку файла на сервер
    using (var client = new HttpClient())
    {
        var requestContent = new MultipartFormDataContent();
        var content = new ByteArrayContent(data);
        content.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
        requestContent.Add(content, "file", $"file.{fileExtension}");

        var response = client.PostAsync(serverUrl, requestContent).Result;
        return Encoding.Default.GetString(await response.Content.ReadAsByteArrayAsync());
    }
}

В зависимости от того, где мы берем файл: локально или из интернета, есть две реализации метода GetBytes.
Для файла, взятого из интернета, нам достаточно передать ссылку на этот файл в данный метод:

private byte[] GetBytes(string fileUrl)
{
    using (var webClient = new WebClient())
    {
        return webClient.DownloadData(fileUrl);
    }
}

Если мы используем локальный файл, то нужно передать путь к файлу в следующий метод:

private byte[] GetBytes(string filePath)
{
    return File.ReadAllBytes(filePath);
}

Загрузить документ(голосовое сообщение) для отправки в сообщения из массива байт.

Для загрузки из файла можно воспользоваться тем же методом, заранее прочитав все байты документа при помощи File.ReadAllBytes(path)

/// <summary>
/// Загружает документ на сервер ВК.
/// </summary>
/// <param name="vkApi">Вк апи.</param>
/// <param name="data">Аттачмент, байты которого будут отправлены на сервер</param>
/// <param name="docMessageType">Тип документа - документ или аудиосообщение.</param>
/// <param name="peerId">Идентификатор назначения</param>
/// <param name="filename">Итоговое название документа</param>
/// <returns>Аттачмент для отправки вместе с сообщением.</returns>
public static async Task<MediaAttachment> LoadDocumentToChatAsync(VkApi vkApi, byte[] data,
    DocMessageType docMessageType, long peerId, string filename)
{
    var uploadServer = vkApi.Docs.GetMessagesUploadServer(peerId, docMessageType);

    var r = await UploadFile(uploadServer.UploadUrl, data);
    var documents = vkApi.Docs.Save(r, filename ?? Guid.NewGuid().ToString()); 

    if (documents.Count != 1)
        throw new ArgumentException($"Error while loading document attachment to {uploadServer.UploadUrl}");

    return documents[0];
}

/// <summary>
/// Загружает массив байт на указанный url
/// </summary>
/// <param name="url">Адрес для загрузки</param>
/// <param name="data">Массив данных для загрузки</param>
/// <returns>Строка, которую вернул сервер.</returns>
public static async Task<string> UploadFile(string url, byte[] data) {
    using (var client = new HttpClient()) {
        var requestContent = new MultipartFormDataContent();
        var documentContent= new ByteArrayContent(data);
        documentContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
        requestContent.Add(documentContent, "file", "audio.webm");

        var response = await client.PostAsync(url, requestContent);

        return Encoding.ASCII.GetString(await response.Content.ReadAsByteArrayAsync());
    }
}

Как отправить сообщение с клавиатурой?

Для примера используем простую клавиатуру с одной кнопкой, имеющей надпись "Привет":

var keyboard = new MessageKeyboard
{
    Buttons = new List<List<MessageKeyboardButton>>
    {
        new List<MessageKeyboardButton>
        {
            new MessageKeyboardButton
            {
                Action = new MessageKeyboardButtonAction
                {
                    Type = KeyboardButtonActionType.Text, //Тип кнопки клавиатуры
                    Label = "Привет", //Надпись на кнопке
                },
                Color = KeyboardButtonColor.Default //Цвет кнопки
            }
        }
    }
};

Так же есть построитель клавиатур который предосталяет удобный Fluent интерфейс

var keyboard = new KeyboardBuilder()
                .AddButton("Подтвердить", "btnValue", KeyboardButtonColor.Primary)
                .SetInline(false)
                .SetOneTime()
                .AddLine()
                .AddButton("Отменить", "btnValue", KeyboardButtonColor.Primary)
                .Build();

Для отправки данной клавиатуры нужно просто передать ее при отправке сообщения в свойстве Keyboard:

Api.Messages.Send(new MessagesSendParams
{
    UserId = 12345678, //Id получателя
    Message = "Message", //Сообщение
    Keyboard = keyboard, // Клавиатура
    RandomId = new Random().Next(999999) //Уникальный идентификатор
});

Подробную информацию о клавиатурах можно найти здесь.


Как прикрепить карту к сообщению?

Api.Messages.Send(new MessagesSendParams
{
    UserId = 12345678, //Id получателя
    Message = "Message", //Сообщение
    Lat = 55.7531773, //Ширина
    Longitude = 37.6157659, //Долгота
    RandomId = new Random().Next(999999) //Уникальный идентификатор
});

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


Как обрабатывать входящие сообщения пользователя (так же такие события как, к примеру, смена аватарки беседы и т.п.)

Тут можно посмотреть коды событий

Класс обработчика:

class VKMessageManager
{
    private VkApi _api = new VkApi();
    private ulong ts;
    private ulong? pts;

    //Событие для уведомления о новом сообщении
    public event Action<Message, User> OnNewMessage;

    public VKMessageManager() 
    {
        //Авторизуемся с учетной записью пользователя. 
        //Для обхода блокировки сообщений используем ApplicationId какого-нибудь официально зарегистрированного приложения
        //либо используем Bypass. В примере ApplicationId приложения Kate Mobile. 
        _api.Authorize(new ApiAuthParams() {
            ApplicationId = 2685278,
            Login = "login", //email или телефон
            Password = "password", //пароль от учетной записи
            Settings = Settings.All //берем полный доступ
        });
    }
    
    public void StartMessagesHandling() 
    {
        //Соединяемся с сервером Long Poll запросов и получаем необходимые ts и pts
        LongPollServerResponse longPoolServerResponse = _api.Messages.GetLongPollServer(needPts: true);
        ts = Convert.ToUInt64(longPoolServerResponse.Ts);
        pts = longPoolServerResponse.Pts;

        //В отдельном потоке запускаем метод, который будет постоянно опрашивать Long Poll сервер на наличие новых сообщений
        new Thread(LongPollEventLoop).Start();
    }

    public void LongPollEventLoop() 
    {
        //Запускаем бесконечный цикл опроса
        while (true) {
            //Отправляем запрос на сервер
            LongPollHistoryResponse longPollResponse = _api.Messages.GetLongPollHistory(new MessagesGetLongPollHistoryParams() {
                Ts = ts,
                Pts = pts, 
                Fields = UsersFields.Photo100 //Указывает поля, которые будут возвращаться для каждого профиля. В данном примере для каждого отправителя сообщения получаем фото 100х100
            });

            //Получаем новый pts
            pts = longPollResponse.NewPts;

            //Тут пробегаемся по массиву событий
            for (int i = 0; i < longPollResponse.History.Count; i++) {
                //И обрабатываем код события
                switch (longPollResponse.History[i][0]) {
                    //Код 4 - новое сообщение
                    case 4:
                        //Тут логика обработки сообщения
                        //К примеру, возбуждаем событие
                        OnNewMessage?.Invoke(
                            longPollResponse.Messages[i], 
                            longPollResponse.Profiles
                             .Where(u => u.Id == longPollResponse.Messages[i].FromId)
                             .FirstOrDefault()
                        );

                        //longPollResponse.Messages[i] - сообщение
                        //longPollResponse.Profiles.Where(u => u.Id == longPollResponse.Messages[i].FromId).FirstOrDefault() - отправитель сообщения
                        break;
                }
            }
        }
    }
}

Использование:

//В консольном приложении этот код помещается в public static void Main()
VKMessageManager manager = new VKMessageManager();
manager.OnNewMessage += (message, sender) => {
    //Обрабатываем входящее сообщение
};
manager.StartMessagesHandling();

Как получить ссылку на аудиозапиись в формате mp3?

public static Uri DecodeAudioUrl(this Uri audioUrl)
{
    var segments = audioUrl.Segments.ToList();

    segments.RemoveAt((segments.Count - 1) / 2);
    segments.RemoveAt(segments.Count - 1);

    segments[segments.Count - 1] = segments[segments.Count - 1].Replace("/", ".mp3");

    return new Uri($"{audioUrl.Scheme}://{audioUrl.Host}{string.Join("", segments)}{audioUrl.Query}");
}
//...
audio.Url.DecodeAudioUrl();

Решение по обходу блокировки API Audio https://github.com/hikiblade/VkNet.AudioBypass