Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4475.html
 

Работа с API vkontakte #2

Опубликовано 28.05.2011 г. 22:04

Чуть более полу года назад была опубликована статья Работа с API vkontakte, в которой был представлен класс для работы с этим самым API. Класс реализовывал вызов различных методов, самостоятельно формировал строку запроса с сигнатурами и т.п. Теперь все немного поменялось, авторизация основывается на протоколе OAuth 2, вызов методов стал происходить немного иначе. Мой плагин для Outlook добросовестно перестал работать из за ошибок с авторизацией. Авторизацию я подправил, и видоизменил и сам класс.

Вообще когда я думал как же там вообще авторизоваться, то пришла в голову идея, а не сделать ли невизуальный компонент для работы. Ну или по крайней мере некоторый модуль, где помимо самого этого класса реализовать собственные исключения, объекты для работы с форматами XML или JSON в зависимости от API_FORMAT. Пока что JSON не поддерживается, но заготовка под это есть. Начинается весь модуль с определения нескольких констант и перечислений для формата запроса и прав доступа.
type
    TApiFormat = (afXML, afJSON);

    TAccessScope = (asNotify, asFriends, asPhotos, asAudio, asVideo, asOffers, asQuestinos, asPages,
                    asNotes, asMessages, asWall, asOffline);
    TScopeSet = set of TAccessScope;

const
    //mask =  1 shl ScopeMask[x]
    ScopeMask : array[TAccessScope] of byte = (0, 1, 2, 3, 4, 5, 6, 7, 11, 12, 13, 16);
массив ScopeMask нужен для проверки битовой маски прав доступа. API функция getUserSettings все равно возвращает текущие права в виде битовой маски. Так что в массиве указаны степени двойки для соответствующих прав. Приведу общее описание класса, и несколько поясняющих комментариев. После чего расскажу о процедуре авторизации, и функции вызова методов API. Описание класса приводить не буду, ибо в конце статью он будет прицеплен. А лучше поясню как его можно использовать.

Пример использования:

const email = 'login@login.com';
      pwd   = 'password';
var session : TSession;
    photoURL : string;
    uNode : IXMLNode;
    ms : TMemoryStream;
    jpImg : TJPEGImage;
begin
    session := TSession.Create(APP_ID);
    session.Scope := [asWall, asOffline];
    session.ApiFormat := afXML;
    try
        with session do begin
            if not Login(email, pwd) then exit;

            edit1.text := UserID;

            Method := 'getProfiles';
            Params['fields'] := 'uid,first_name,last_name,nickname,photo';
            Params['uids']   := UserID;

            requestData();
        end;
        ContentMemo.text := session.XML.XML;
        uNode := session.XML.ChildNodes[0];
        photoURL := uNode.ChildValues['photo'];

        ms := session.GetBinaryData(photoUrl);
        jpImg := TJpegImage.Create();
        jpImg.LoadFromStream(ms);
        image1.Picture.Graphic := jpImg;
    finally
        session.Free();
        if assigned(ms) then ms.Free();
    end;
end;
  1. Создаем сессию (наш класс) session : TSession. Параметром конструктора является ID вашего приложения.
  2. Устанавливаем необходимые нам права доступа, например, scope = [asWall]
  3. устанавливаем формат выдачи XML: ApiFormat = afXML. Это можно не делать, по умолчанию и так XML.
  4. Начинаем защищенный блок. В случае ошибок вызовов у нас будут создаваться соответствующие исключительные ситуации. Вернее пока что просто исключение с нужным сообщением. Но вообще можно дописать свои классы исключений, если решу вдруг продолжить развитие класса.
  5. вызываем метод login. в случае успеха возвращает true. В случае ошибок вызывает исключения.
  6. После того как залогинились можем получить UserID пользователя, если надо.
  7. Устанавливаем нужный метод Method и параметры через Params[] и вызываем его, используя RequestData()
  8. Всю информацию можем залить в мемо с помощью XML : IXMLNode - это корень документа который получили в ответ.
  9. Берем узел,описывающий пользователя - uNode, и извлекаем из него URL фотографии.
  10. В виде потока получаем бинарные данные - фотографию по этой ссылке - getBinaryData.
  11. Создаем TJPEGImage и загружаем полученную фотографию в компонент Image1 на форме.
Теперь расскажу о двух наиболее интересных процедурах: как происходит извлечение данных - RequestData и авторизация - Login.

Обращение к методам API

Собственно выше уже был пример обращения к методу. Для этого надо указать его имя и заполнить параметры. Как из этого собирается запрос?
procedure TSession.RequestData();
var tsNow : int64;
begin
    if not FConnected then exit;
    if trim(FMethod) = '' then
        raise Exception.Create('undefined method');

    FRequestURL  := API_URL + FMethod;
    if FApiFormat = afXML then
        FRequestURL := FRequestURL + '.xml';


    FParams.values['access_token'] := FToken;
    FRequestURL := FRequestURL + '?' + FParams.DelimitedText;




    tsNow := TStopwatch.GetTimeStamp;
    if (tsNow - FTimestamp ) < 350000 then sleep(333);
    FTimestamp := TStopwatch.GetTimeStamp;


    FXmlDoc.XML.text :=  http.Get(FRequestURL);
    FXmlDoc.Active := true;
    FXmlRoot := FXmlDoc.DocumentElement;

    if FXmlRoot.NodeName = 'error' then
        raise Exception.Create('RequestData error:'#13 +
                               'url: ' + FParams.DelimitedText + #13+
                               'error : ' + FxmlRoot.XML);
end;
  1. Если мы еще не залогинились, то выходим. Если у нас не указан метод, то вызываем исключение.
  2. Создаем базовую URL - к URL для доступа к API (https://api.vkontakte.ru/method/) прибавляем имя метода.
  3. Если используем формат xml то нам необходимо указать это, добавив .xml к имени метода.
  4. К параметра добавляем токен. После чего все параметры склеиваем в строку. FParams это TStringList у которого разделитель настроен на знак & так что свойство DelimitedText возвращает нам все параметры уже в нужном виде.
  5. У API есть ограничение на количество запросов в секунду. Вообще это не хорошо делать в главном потоке.
  6. Загружаем документ и проверяем не было ли ошибки.
для учета форматов надо конечно немного переделать.

Авторизация

Первое что нам понадобится - строка формы авторизации:
function TSession.getLoginURL(): string;
const LOGIN_URL    = 'http://api.vkontakte.ru/oauth/authorize?client_id=%d&redirect_uri=%s&scope=%s&response_type=token&display=page';
      REDIRECT_URL = 'http://api.vkontakte.ru/blank.html';
begin
    result := Format(LOGIN_URL, [FAppID, REDIRECT_URL, getScopeString()]);
end;
тут используется вспомогательная функция getScopeString(), которая переводит множество TScopeSet в строку названий. Теперь сам метод авторизации:
function TSession.Login(const email, pwd: string): boolean;
var content : string;
    AnchorPos : integer;
//    function getLoginFormHiddenField(html, field : string; quote : char):string;
begin
    result := false;
    content := http.Get(LoginURL);

    with FParams do begin
        values['act']   := 'login';
        values['soft']  := '1';
        values['q']     := '1';
        values['from_host'] := 'api.vkontakte.ru';
        values['to']    := getLoginFormHiddenField(content, 'to', '"');
        values['expire']:= '0';
        values['email'] := email;
        values['pass']  := pwd;
    end;
    content := http.Post('http://login.vk.com/', FParams);

    try
        content := http.Get(FRedirectURL);
    except
        on e : EIdIOHandlerPropInvalid do begin
            //https IO Handler exception
        end;
    end;
    http.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);

    content := http.Get(FRedirectURL);

    AnchorPos := pos('#', FRedirectURL);
    if AnchorPos = 0 then
        raise Exception.Create('token not found');

    FParams.DelimitedText := copy(FRedirectURL, AnchorPos + 1, length(FRedirectURL) - AnchorPos);

    FToken  := Params['access_token'];
    FUserID := Params['user_id'];

    FConnected := length(FToken) > 0;
    result := FConnected;

    CheckUserSettings()
end;
Получаем страницу с формой логина. Нам требуется сформировать такую же форму для отправки POST запроса. Из самой формы мы выдираем значения поля "to" с помощью функции getLoginFormHiddenField(). Ее код я не привожу, чтобы место не занимать. Далее мы заполняем параметры, как это делается в html-форме. К слову сказать, нам не обязательно заполнять именно все параметры. Некоторые можно пропустить, по крайней мере раньше при старой авторизации так было. Отправляем то, что получилось, методом POST. В ответ нас редиректит на новую страницу. В обработчике события редиректа мы запоминаем куда нас направили в переменную FRedirectURL. И далее загружаем эту страницу. В ответ нас опять редиректит, но уже на страницу с протоколом https. Здесь мы получаем исключение, поскольку наш IOHandler с таким протоколом работать не умеет. Исключение мы это добросовестно гасим, после чего создаем новый IOHandler, поддерживающий https. Вновь загружаем страницу на которую нас перемещают. И в результате нам выдают токен. Чтобы самый этот токен извлечь, мы берем URL с токеном - это запомненный FRedirectURL. Вырезаем из нее часть после якорной ссылки. Все остатки записываем в FParams, который добросовестно разбивает их на строки по символу &. Извлекаем токен, и ID пользователя. Если токен не нулевой длины, то считаем, что мы залогинились успешно. Следующий шаг - проверим, удалось ли получить нужные права - CheckUserSettings. Если прав недостаточно, то эта функция вызывает исключение. Такая вот история. Нужно еще разобраться с тем как форму POST запросом отправлять, а то он там кодирует параметры, и че то в зависимости от того есть ли всякие бредовые символы в пароле может и не коннектить. Если кому то интересно, то можете скачать исходник:

исходный код

чтобы все работало вам необходимо иметь библиотеки для работы с https их вы можете найти здесь: http://www.indyproject.org/Sockets/SSL.EN.aspx Если вы таки решите использовать данный класс, то дайте знать, возможно, можно будет расширить его функционал, и действительно сделать из этого более интересный модуль. Дописать web-авторизацию, исключения, обработчики полученных данных в зависимости от формата XML или JSON и т.п. Сам я никаких приложений для контакта не пишу, так что без откликов данный модуль врядли будет сильно развиваться.
Метки:  vkontakte API  |  http 

Комментарии

Isaev
07.02.2017 в 19:52
Исходный код не доступен, т.к. больше не актуален или случайно битая ссылка?
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно