Работа с Vkontakte.ru API в iOS приложении

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

Проект с примером лежит на github.com – https://github.com/maiorov/VKAPI

Документацию по методам API ВКонтакте можно почитать тут

Статьи из других источников на эту тему:
1. appleinsider.ru
2. imaladec.net

Итак, несколько этапов в работе с VK API:
1. Авторизация пользователя через UIWebView и получение accessToken'a и идентификатора пользователя.
2. Использование VK API, запросы и получение данных:
2.1 Отправка текста на стену пользователя
2.1.1 Captcha
2.2 Отправка изображения на стену
2.2.1 Получение url сервера ВКонтакте для загрузги изображения
2.2.2 Формируем POST запрос
2.2.3 Создаем запрос на сохранение фото на сервере ВКонтакте
2.2.4 Размещаем фото на стене
3. Logout пользователя

Часть 0. Регистрация приложения
Как выяснилось, не самая тривиальная часть операции, из-за того, что ВКонтакте несколько форм создания mobile-приложений! Для правильной регистрации нужно воспользоваться ссылкой http://vkontakte.ru/editapp?act=create&site=1, выбрать тип приложения Standalone-приложение. Параметры в настройках можно не указывать. Нужно лишь скопировать ID приложения. В моем примере для этого параметра используется appID.

Часть 1. Авторизация
Для работы по протоколу Oauth 2.0 с VK API нам понадобится accessToken и user_id. Мы можем получить его отправив запрос на сервер VK через веб-браузер UIWebView, в ответ на запрос сервер ВКонтакте выдаст форму авторизации пользователя, после прохождения авторизации мы получим accessToken, user_id (id пользователя, который ввел свои данные), expires_in (время действия токена). Для этого в новом проекте создадим view-controller, назовем его например vkLoginViewController. Это будет контроллер отвечающий за авторизацию пользователя, при успешной авторизации, он сохраняет полученные параметры в NSUserDefaults и вызывает метод делегата authComplete.

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

Добавив в контроллер UIWebView *vkWebView, мы делаем запрос:
    NSString *authLink = [NSString stringWithFormat:@"http://api.vk.com/oauth/authorize?client_id=%@&scope=wall,photos&redirect_uri=http://api.vk.com/blank.html&display=touch&response_type=token", appID];
    NSURL *url = [NSURL URLWithString:authLink];
    
    [vkWebView loadRequest:[NSURLRequest requestWithURL:url]];

Тут client_id это id вашего приложения, scope=wall,photos – это права доступа, которые приложение запрашивает у пользователя (wall размещение на стене, photos нужно чтобы размещать изображения на стене)

В функции webViewDidFinishLoad получаем ответ от сервера ВКонтакте:
-(void)webViewDidFinishLoad:(UIWebView *)webView {
    // Если есть токен сохраняем его
    if ([vkWebView.request.URL.absoluteString rangeOfString:@"access_token"].location != NSNotFound) {
        NSString *accessToken = [self stringBetweenString:@"access_token=" 
                                                andString:@"&" 
                                              innerString:[[[webView request] URL] absoluteString]];
        
        // Получаем id пользователя, пригодится нам позднее
        NSArray *userAr = [[[[webView request] URL] absoluteString] componentsSeparatedByString:@"&user_id="];
        NSString *user_id = [userAr lastObject];
        NSLog(@"User id: %@", user_id);
        if(user_id){
            [[NSUserDefaults standardUserDefaults] setObject:user_id forKey:@"VKAccessUserId"];
        }
        
        if(accessToken){
            [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:@"VKAccessToken"];
            // Сохраняем дату получения токена. Параметр expires_in=86400 в ответе ВКонтакта, говорит сколько будет действовать токен.
            // В данном случае, это для примера, мы можем проверять позднее истек ли токен или нет
            [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"VKAccessTokenDate"];
            [[NSUserDefaults standardUserDefaults] synchronize];
        }
        
        NSLog(@"vkWebView response: %@",[[[webView request] URL] absoluteString]);
        [(ViewController *)delegate authComplete];
        [self dismissModalViewControllerAnimated:YES];
    } else if ([vkWebView.request.URL.absoluteString rangeOfString:@"error"].location != NSNotFound) {
        NSLog(@"Error: %@", vkWebView.request.URL.absoluteString);
        [self dismissModalViewControllerAnimated:YES];
    }
    
}


После этого, мы получили все необходимое: accessToken, user_id. И передаем делегату, что авторизация успешно пройдена, больше нам этот контроллер не нужен и мы его убираем:
[(ViewController *)delegate authComplete];
[self dismissModalViewControllerAnimated:YES];


Часть 2. Запросы к API
Теперь начинается самое интересное! В примере в контроллере ViewController я подготовил несколько функций, по которым вы сможете понять как работать с API.

2.1 Отправка текста на стену пользователя
Возьмем, для примера, функцию отправки текста на стену:
- (void) sendText {
    NSString *user_id = [[NSUserDefaults standardUserDefaults] objectForKey:@"VKAccessUserId"];
    NSString *accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"VKAccessToken"];
    
    NSString *text = @"Работать с ВКонтакте API легко!";
    
    // Создаем запрос на размещение текста на стене
    NSString *sendTextMessage = [NSString stringWithFormat:@"https://api.vk.com/method/wall.post?owner_id=%@&access_token=%@&message=%@", user_id, accessToken, [self URLEncodedString:text]];
    NSLog(@"sendTextMessage: %@", sendTextMessage);
    
    // Если запрос более сложный мы можем работать дальше с полученным ответом
    NSDictionary *result = [self sendRequest:sendTextMessage withCaptcha:NO];
    // Если есть описание ошибки в ответе
    NSString *errorMsg = [[result objectForKey:@"error"] objectForKey:@"error_msg"];
    if(errorMsg) {
        [self sendFailedWithError:errorMsg];
    } else {
        [self sendSuccessWithMessage:@"Текст размещен на стене!"];
    }
}

Для простоты примера, я храню данные в NSUserDefaults. Авторизация пройдена, значит у нас есть все необходимое чтобы составить запрос. Используя метод wall.post, передаем id пользователя (owner_id) и токен (access_token), сообщение text для параметра message мы предварительно прогоняем через функцию URLEncodedString:
// Функция преобразования текста в подходящий для передачи формат
- (NSString *)URLEncodedString:(NSString *)str
{
    NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                           (CFStringRef)str,
                                                                           NULL,
																		   CFSTR("!*'();:@&=+$,/?%#[]"),
                                                                           kCFStringEncodingUTF8);
    [result autorelease];
	return result;
}


После того, как мы подготовили запрос, нужно отправить его на сервер с помощью функции:
[self sendRequest:sendTextAndLinkMessage withCaptcha:NO];
Как вы можете заметить, фунцкция принимает параметр withCaptcha:(BOOL) это понадобится нам в случае, если сервер затребует ввод капчи у пользователя для подтверждения действий. Так как пока что капча не требуется, мы передаем NO.

Собственно, сама функция отправки запроса:
- (NSDictionary *) sendRequest:(NSString *)reqURl withCaptcha:(BOOL)captcha {
    // Если это запрос после ввода капчи, то добавляем в запрос параметры captcha_sid и captcha_key
    if(captcha == YES){
        NSString *captcha_sid = [[NSUserDefaults standardUserDefaults] objectForKey:@"captcha_sid"];
        NSString *captcha_user = [[NSUserDefaults standardUserDefaults] objectForKey:@"captcha_user"];
        // Добавляем к запросу данные для капчи. Не забываем, что введенный пользователем текст нужно обработать.
        reqURl = [reqURl stringByAppendingFormat:@"&captcha_sid=%@&captcha_key=%@", captcha_sid, [self URLEncodedString: captcha_user]];
    }
    NSLog(@"Sending request: %@", reqURl);
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:reqURl] 
                                                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData 
                                                        timeoutInterval:60.0]; 
    
    // Для простоты используется обычный запрос NSURLConnection, ответ сервера сохраняем в NSData
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    
    // Если ответ получен успешно, можем его посмотреть и заодно с помощью JSONKit получить NSDictionary
    if(responseData){
        NSDictionary *dict = [[JSONDecoder decoder] parseJSONData:responseData];
        
        // Если есть описание ошибки в ответе
        NSString *errorMsg = [[dict objectForKey:@"error"] objectForKey:@"error_msg"];
        
        NSLog(@"Server response: %@ \nError: %@", dict, errorMsg);
        
        // Если требуется ввод капчи. Тут я проверяю на строку Captcha needed, хотя лучше использовать код ошибки.
        if([errorMsg isEqualToString:@"Captcha needed"]){
            isCaptcha = YES;
            // Сохраняем параметры для капчи
            NSString *captcha_sid = [[dict objectForKey:@"error"] objectForKey:@"captcha_sid"];
            NSString *captcha_img = [[dict objectForKey:@"error"] objectForKey:@"captcha_img"];
            [[NSUserDefaults standardUserDefaults] setObject:captcha_img forKey:@"captcha_img"];
            [[NSUserDefaults standardUserDefaults] setObject:captcha_sid forKey:@"captcha_sid"];
            // Сохраняем url запроса чтобы сделать его повторно после ввода капчи
            [[NSUserDefaults standardUserDefaults] setObject:reqURl forKey:@"request"];
            [[NSUserDefaults standardUserDefaults] synchronize];
            
            [self getCaptcha];
        }
        
        return dict;
    }
    return nil;
}

Функция возвращает NSDictionary ответ сервера, для его формирования используется JSONKit, так как по-умолчанию ВКонтакте отвечает в формате JSON.

Captcha
Если вдруг сервер решил, что пользвателю нужно ввести капчу, то он, в ответ на запрос, вернет ошибку с текстом: Captcha needed и параметрами – captcha_sid (это id капчи), captcha_img (ссылка на изображение капчи). Вооружившись этими параметрами и заодно сохранив запрос, по которому сервер нам так ответил, мы вызываем функцию ввода капчи:
[self getCaptcha];

Рассмотрим ее чуть подробнее:
- (void) getCaptcha {

    NSString *captcha_img = [[NSUserDefaults standardUserDefaults] objectForKey:@"captcha_img"];
    UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Введите код:\n\n\n\n\n"
                                                          message:@"\n" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    
    UIImageView *imageView = [[[UIImageView alloc] initWithFrame:CGRectMake(12.0, 45.0, 130.0, 50.0)] autorelease];
    imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:captcha_img]]];
    [myAlertView addSubview:imageView];
    
    UITextField *myTextField = [[[UITextField alloc] initWithFrame:CGRectMake(12.0, 110.0, 260.0, 25.0)] autorelease];
    [myTextField setBackgroundColor:[UIColor whiteColor]];
    
    // Отключаем автокорректировку
    myTextField.autocorrectionType = UITextAutocorrectionTypeNo;
    // Отключаем автокапитализацию
    myTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
    myTextField.tag = 33;
    
    [myAlertView addSubview:myTextField];
    [myAlertView show];
    [myAlertView release];
}

- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if(isCaptcha && buttonIndex == 1){
        isCaptcha = NO;
        
        UITextField *myTextField = (UITextField *)[actionSheet viewWithTag:33];
        [[NSUserDefaults standardUserDefaults] setObject:myTextField.text forKey:@"captcha_user"];
        NSLog(@"Captcha entered: %@",myTextField.text);
        
        // Вспоминаем какой был последний запрос и делаем его еще раз
        NSString *request = [[NSUserDefaults standardUserDefaults] objectForKey:@"request"];
        
        NSDictionary *newRequestDict =[self sendRequest:request withCaptcha:YES];
        NSString *errorMsg = [[newRequestDict  objectForKey:@"error"] objectForKey:@"error_msg"];
        if(errorMsg) {
            [self sendFailedWithError:errorMsg];
        } else {
            [self sendSuccessWithMessage:@"Капча обработана успешно и запрос выполнен!"];
        }
    }
}

Используя стандартный UIAlertView, с добавлением UIImageView и UITextField, и параметр captcha_img, мы даем пользователю возможность ввести капчу. После того как капча введена, в функции делегата UIAlertView сохраняем то, что ввел пользователь в NSUserDefaults с ключем captcha_user и используя ранее сохраненный url запроса, делаем его повторно, на этот раз указав в параметре withCaptcha: YES. В итоге, перед отправкой запроса, к нему добавляюся параметы captcha_sid и captcha_key (то что ввел пользователь):
- (NSDictionary *) sendRequest:(NSString *)reqURl withCaptcha:(BOOL)captcha {
    // Если это запрос после ввода капчи, то добавляем в запрос параметры captcha_sid и captcha_key
    if(captcha == YES){
        NSString *captcha_sid = [[NSUserDefaults standardUserDefaults] objectForKey:@"captcha_sid"];
        NSString *captcha_user = [[NSUserDefaults standardUserDefaults] objectForKey:@"captcha_user"];
        // Добавляем к запросу данные для капчи. Не забываем, что введенный пользователем текст нужно обработать.
        reqURl = [reqURl stringByAppendingFormat:@"&captcha_sid=%@&captcha_key=%@", captcha_sid, [self URLEncodedString: captcha_user]];
    }
...

Капча введена, запрос отправлен, текст должен быть успешно размещен на стене пользователя!

2.2 Отправка изображения на стену
Часто встречается необходимость разместить фото из приложения на стене пользователя. Чтобы решить эту задачу нужно пройти следующие этапы:
  1. Запрос url сервера ВКонтакте для загрузки нашего изображения (photos.getWallUploadServer)
  2. По полученной ссылке в ответе сервера отправляем изображение методом POST
  3. Получив в ответе hash, photo, server, отправлем команду на сохранение фото на стене (photos.saveWallPhoto)
  4. Получив в ответе photo id, делаем запрос на размещение на стене картинки с помощью wall.post, где в качестве attachment указываем photo id


2.2.1 Получение url сервера ВКонтакте для загрузки изображения
- (IBAction)sendImageAction:(id)sender {
    if(!isAuth) return;
    UIImage *image = [UIImage imageNamed:@"test.jpg"];
    NSString *user_id = [[NSUserDefaults standardUserDefaults] objectForKey:@"VKAccessUserId"];
    NSString *accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"VKAccessToken"];
    
    // Этап 1 
       
    NSString *getWallUploadServer = [NSString stringWithFormat:@"https://api.vk.com/method/photos.getWallUploadServer?owner_id=%@&access_token=%@", user_id, accessToken];
    
    NSDictionary *uploadServer = [self sendRequest:getWallUploadServer withCaptcha:NO];
    
    // Получаем ссылку для загрузки изображения
    NSString *upload_url = [[uploadServer objectForKey:@"response"] objectForKey:@"upload_url"];

...

Теперь, получив ссылку для загрузки изображения, нам нужно сделать POST запрос с вложенным изображением. Для этого изображение преобразуем в NSData и создаем запрос с помощью функции
[self sendPOSTRequest:upload_url withImageData:imageData];


2.2.2 Формируем POST запрос
...
// Этап 2 
    // Преобразуем изображение в NSData
    NSData *imageData = UIImageJPEGRepresentation(image, 1.0f);
    
    NSDictionary *postDictionary = [self sendPOSTRequest:upload_url withImageData:imageData];
    
    // Из полученного ответа берем hash, photo, server 
    NSString *hash = [postDictionary objectForKey:@"hash"];
    NSString *photo = [postDictionary objectForKey:@"photo"];
    NSString *server = [postDictionary objectForKey:@"server"];
...

Функцию формирующую POST-запрос вы можете посмотреть в примере.

2.2.3 Создаем запрос на сохранение фото на сервере ВКонтакте
...
// Этап 3 
    // Создаем запрос на сохранение фото на сервере вконтакте, в ответ получим id фото
    NSString *saveWallPhoto = [NSString stringWithFormat:@"https://api.vk.com/method/photos.saveWallPhoto?owner_id=%@&access_token=%@&server=%@&photo=%@&hash=%@", user_id, accessToken,server,photo,hash];
    
    NSDictionary *saveWallPhotoDict = [self sendRequest:saveWallPhoto withCaptcha:NO];
    
    NSDictionary *photoDict = [[saveWallPhotoDict objectForKey:@"response"] lastObject];
    NSString *photoId = [photoDict objectForKey:@"id"];
...

В ответ получаем photo id загруженной фотографии, теперь осталось сделать обычный запрос wall.post, где в качестве параметра attachment указать photo id. Если нужно еще добавить ссылку, то достаточно после photo id, через запятую указать ее.

2.2.4 Размещаем фото на стене
...
// Этап 4 
    // Постим изображение на стену пользователя
     NSString *postToWallLink = [NSString stringWithFormat:@"https://api.vk.com/method/wall.post?owner_id=%@&access_token=%@&message=%@&attachment=%@", user_id, accessToken, [self URLEncodedString:@"К изображению можно добавить текст и ссылку"], photoId];
    
    NSDictionary *postToWallDict = [self sendRequest:postToWallLink withCaptcha:NO];
    NSString *errorMsg = [[postToWallDict  objectForKey:@"error"] objectForKey:@"error_msg"];
    if(errorMsg) {
        [self sendFailedWithError:errorMsg];
    } else {
        [self sendSuccessWithMessage:@"Картинка размещена на стене!"];
    }
    // Ура! Фото должно быть на стене!


Часть 3. Logout пользователя
Чтобы сделать logout достаточно отправить на сервер ВКонтакте запрос:
NSString *logout = @"http://api.vk.com/oauth/logout";

Пример функции logout:
- (IBAction)logout:(id)sender {
    // Запрос на logout
    NSString *logout = @"http://api.vk.com/oauth/logout";
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:logout] 
                                                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData 
                                                        timeoutInterval:60.0]; 
    NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    if(responseData){
        NSDictionary *dict = [[JSONDecoder decoder] parseJSONData:responseData];
        NSLog(@"Logout: %@", dict);
        
        // Приложение больше не авторизовано, можно удалить данные
        isAuth = NO;
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"VKAccessUserId"];
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"VKAccessToken"];
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"VKAccessTokenDate"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
        [self sendSuccessWithMessage:@"Выход произведен успешно!"]; 
    }
}


Несмотря на то, что ВКонтакте отвечает ошибкой, выход пользователя все-таки происходит :)
Logout: {
    error = "invalid_client";
    "error_description" = "client_id is incorrect";
}


Заключение
Надеюсь, что эта статья поможет вам в освоении API Вконтакте. Буду рад комментариям, дополнениям.

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

    +2
    Возникает вопрос: Всё конечно красиво, но вот сколько времени было убито на освоение Object-C? Посоветуйте книги, статьи, мануалы, по поводу разработки под ios.
      +1
      Зная C, Java и еще несколько языков, лично у меня очень мало времени (меньше месяца) ушло на изучение имеено Objective C. Самой большой ошибкой при изучении было не смотреть официальную документацию от Apple. Это и есть ответ на второй вопрос. Если нет проблем с английским то читайте официальную документацию от Apple — там очень хорошо и подробно все разбирается и сам язык и программирование именно под iPhone.
        0
        Лично я это время убитым не считаю :)
        В интернете полно видео-курсов и сайтов с различными туториалами.
        Курсы от Lynda.com:
        Objective-C Essential Training
        iOS 4: Building Data-Driven Applications
        Куча видео от STANFORD
        Apple WWDC 2010, 2011 video sessions
        Информации огромное количество, было бы время все изучить.
          0
          Есть очень хорошая книжка именно по Objective-C и Cocoa, сам по ней учился, сначала было тяжело переварить, так как ООП не знал толком, но сейчас практически всё до меня дошло. :)
          Cocoa and Objective-C: Up and Running
          Foundations of Mac, iPhone, and iPod touch programming
          .
          Пробовал читать книги на русском, но из тех, что переведены, не нашел ни одной толковой.
          0
          пишут, что сейчас публикацию на стене отключили? это правда?
            0
            Насколько мне известно, этот запрет не относится к Standalone-приложениям. Сейчас этот метод работает, вы можете проверить с помощью проекта на github.
              0
              Публикация на стенах доступна только для Standalone приложений или IFrame/flash приложений. А для серверной аутентификации данный метод недоступен =(
              +3
              Я видимо слабак и тряпка, но синтаксис Object-C повергает меня в уныние и обнуляет самооценку.
                +5
                На самом деле, он представляет проблему только первый месяц, потом начинаешь писать и читать этот код совершенно спокойно.
                  0
                  А мне наоборот синтаксис Objective-C кажется предельно простым. Возможно сказывается то, что перешел на него с С, не зная в то время других объектно-ориентированных языков.
                    0
                    Синтаксис меня очень обрадовал и успешно уложился в мою голову в первый же день обучения.
                    0
                    Единственный минус авторизации через UIWebView — отсутствие общих cookies, в своем приложении я делал через задание приложению URL схемы, и редиректу на него после авторизации.

                    Правда справедливости ради в моему случае ВКонтакте требовался как способ авторизации на своем ресурсе, и результат авторизации я предварительно проверял у себя на сервере.
                      0
                      Надеялся, что кто-то написал библиотеку для контакта, но и так местами полезно, например, про капчу вообще не знал, так как контактом не пользовался года 2 наверное.
                        0
                        Важный момент:

                        NSString *logout = [NSString stringWithFormat:@«api.vk.com/oauth/logout»];

                        когда я передаю запрос на logout мне приходит ошибка
                        logout string {«error»:«invalid_client»,«error_description»:«client_id is incorrect»}

                        maiorov.com/ios-development/integratsiya-vkontakte-api-v-ios-prilozhenie/
                        Здесь Вы писали внизу, что выход при этом все-таки происходит. Но это совсем не так. При следующей авторизации он сразу редиректит на страницу Login Success. Никто не замечал подобную проблему? Хотелось бы знать решение. Пробовал уже поставлять туда параметры для client_id не прокатывает.
                          0
                          Не знаю нашли вы решение или нет, но оно очевидное, при логауте необходимо удалять куки, например так:
                          NSHTTPCookie *cookie;
                          NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
                          for (cookie in [storage cookies]) {

                          NSString* domainName = [cookie domain];
                          NSRange domainRange = [domainName rangeOfString:@"vk.com"];

                          if(domainRange.length > 0) {
                          [storage deleteCookie:cookie];
                          }
                          }
                            0
                            первый раз вставлял код в коммент, и не догадался нажать предпросмотр, жуть вышла.
                              0
                              Может быть будем звучать странно, но естественно это я попробовал сделать вторым делом. И не поверите но неизвестно почему — он не очищает куки, я несколько раз выводил весь лист из sharedHTTPCookieStorage, действительно находил для vkontakte.ru — удалял их, при перезаходе приложения обратно смотрю в куки — и они там снова есть.
                              Может быть тут какая-то смежность, из-за того что вне приложения например поднят VK клиент на айпаде и в Safari загружена его страница.
                                0
                                Про смежность и тд не знаю, но обратите внимание, что куки я удаляю именно для vk.com собственно и авторизация там же и отлично все работает, после удаления снова просит ввести логин и пароль.
                          0
                          Попробовал ваш пример авторизацию прошел пытаюсь разместить что нибудь у себя на стене но не получается капчу запрашивает пишет что все успешно но на стене ничего нет.

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

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