Skip to content
Egorka_Miller edited this page Dec 29, 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) //Уникальный идентификатор
});

Как работать с payload кнопок:

// У нас есть данная клавиатура, которую мы отправим с сообщением (см.выше):
var keyboard = new KeyboardBuilder()
                .AddButton("Привет", "hello", KeyboardButtonColor.Primary, "main") // "hello" является полезной нагрузкой (payload)
                .SetInline(false)                                                  // "main" является типом кнопки payload
                .Build();

// Воспользуемся обработчиком сообщений VKMessageManager с авторизацией от группы (см.ниже)
VKMessageManager manager = new VKMessageManager();
manager.OnNewMessage += (message, sender) => {

    switch (message.Payload) // Получаем payload кнопок, после чего обрабатываем его
    {
        // payload кнопок приходит в данном формате, "hello" наша полезная нагрузка (payload)
        // "main" тип, который мы указали в кнопке, не указывая его, он будет равен "button"
        case "{\"main\":\"hello\"}": 
          
            await Api.Messages.SendAsync(new MessagesSendParams()
            {
                PeerId = message.PeerId.Value,                      // Отправим сообщение туда, откуда получили
                RandomId = random.Next(int.MinValue, int.MaxValue), // Уникальный идентификатор
                Message = "Кнопка \"Привет\" работает!!!"           // Сообщение
            });
                        
            break;
     }
};
manager.StartMessagesHandling();

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


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

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;


            // Если вы планируете отлавливать ТОЛЬКО СООБЩЕНИЯ, используйте следующий код:
            foreach (var longPollResult in longPollResponse.Messages)
            {
                OnNewMessage?.Invoke(
                    longPollResponse.Messages[0],
                    longPollResponse.Profiles
                        .Where(u => u.Id == longPollResponse.Messages[0].FromId)
                        .FirstOrDefault()
                );
            }
       

            // Если помимо сообщений вы планируете ловить что-то еще, используйте следующий код:
            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[0].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) => {
    //Обрабатываем входящее сообщение
    switch (message.Text.ToLower()) //входящие сообщения преобразуем в нижний регистр во избежании проблем с ним
    {
        case "привет":
          
            await _api.Messages.SendAsync(new MessagesSendParams()
            {
                PeerId = message.PeerId.Value,
                RandomId = random.Next(int.MinValue, int.MaxValue),
                Message = "Привет!!!"
            });
                        
            break;
     }
};
manager.StartMessagesHandling();

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

Инструментарий для ботоводов:

Clone this wiki locally