...

среда, 19 февраля 2014 г.

[Из песочницы] Притворяемся официальным приложением ВКонтакте

Началось всё с того, как мой друг попросил меня опубликовать на его странице от его имени пост с моего iPad'а. Дальше я… Не знаю, как это описать… А потом задумался о том, как же официальные приложения ВК для мобильных устройств и планшетов выполняют какие-либо действия.

Сначала подумал, что приложение отправляет через POST или GET во ВКонтактик какие-то данные. Потом понял, что, скорее всего, приложение авторизуется через API. Зашёл на страницу «Разработчикам» в ВК, выбрал «Standalone/Mobile-приложения», выбрал «OAuth-авторизация». Увидел, как приложение должно авторизовываться. Оно должно создавать окно с диалогом авторизации, с такой ссылкой:

http://ift.tt/1fP0vTz

И так, сначала нужно узнать id официального приложения.

Я решил начать с приложения для iOS, а именно для iPad.

Запостил на своей стене запись с него, открыл на десктопе. Навёл мышку на значок Яблока, и увидел желаемое «vk.com/app3682744». С этим, самым простым, мы справились. Права я решил установить только доступ к стене (и, автоматически, к основной информации), если что-то надо будет ещё добавить, это можно будет сделать потом. И, конечно, нужно включить кроме «wall» ещё и «offline» — доступ к API в любое время со стороннего сервера. Иначе, делать что-либо сможет только сервер, куда приложение отсылает все действия, и через который всё делается (возможно этот сервер — сервер ВК, но я решил не выяснять, так как это мне не нужно. Сервер прописывается в настройках приложения). И так, сейчас наша ссылка выглядит как http://ift.tt/1bg7R5r,offline&redirect_uri=REDIRECT_URI&display=DISPLAY&v=API_VERSION&response_type=token

С redirect_uri разберёмся потом, как с самым сложным. «display» — внешний окна авторизации, page, popup или mobile. Выберем page.

«v» выставим последнюю, 5.7. response_type изменять не нужно, нам получить токен и надо.

Теперь будем думать над «redirect_uri». Нам нужна страница, которая покажет токен. И тут я замечаю «Если Вы разрабатываете браузерное Javascript-приложение...». Понимаю, что именно это нам подходит, так как мы обращаемся со «стороннего сервера». Там написано, что в таком случае надо указать «http://ift.tt/1f6ImiA». Так и сделаем. Теперь наша ссылка выглядит так: http://ift.tt/1f6InD5,offline&redirect_uri=http://ift.tt/1bg7R5B

Переходим по ссылке, и получаем то, что мы и хотели:

image

Нажимаем на «разрешить».

image

Получаем предупреждение, и ссылку в адресной строке в виде http://ift.tt/1f6ImyQ, ТОЕСТЬНАС

Ура! Мы получили, что хотели. А дальше можно экспериментировать с функциями. Попробуем использовать «wall.post», функцию для создания записи на стене. После маленького раздумья понимаем, что надо перейти по ссылке «http://ift.tt/1f6InD9». Проверяем, убеждаемся, что работает. Радуемся.

После поиска среди записей, нахожу id приложения для WindowsPhone — 3502561. Ещё чуть-чуть радуюсь. Решаю для ещё какого-то количества лулзов создать приложения ВУтюг и ВОкно. Их id — 3998121 и 4147789. Ссылка везде точно такая же, только параметр «client_id» надо поменять на id приложения.

Для полной очистки совести решаю написать ещё и от имени Android и iPhone, нахожу их ID (28909846 и 3087106), и тут… Вместо страницы разрешения система выдаёт грозное "{«error»:«invalid_access»,«error_description»:«Security issue»}". Я начинаю думать, в чём дело. Через какое-то время вспоминаю, что приложения для Android и WindowsPhone авторизуются через OAuth. Захожу в документацию. Вижу, что для того, чтобы авторизоваться через OAuth, нужен ключ приложения. Его у меня, понятное дело, нет. Решаюсь на отчаянный шаг — нахожу в интернете официальное приложение ВКонтакте для Android'а, и декомпилирую его. После примерно двухчасовых рысканий по исходникам понимаю, что найти ключ не реально. Начинаю придумывать другой способ. Наконец мне это надоедает, и я начинаю заниматься другими делами. И тут до меня доходит!.. Ведь в первых версиях API была только прямая авторизация, а это значит, что старые версии приложений не работают через OAuth, а, так как ВКонтакте не могло оставить не работающими старые версии, должна быть какая-то лазейка. И тут у меня появляется гениальная идея (всё гениальное — просто): в ссылке для получения токена, например, в http://ift.tt/1f6InD5,offline&redirect_uri=http://ift.tt/1bg7R5B указывается версия API системы, например тут — последняя, 5.7. А если указать старую, например, 3.0? А если вообще не указывать версию? Проверяю на приложении для Android: перехожу по ссылке http://ift.tt/1huYVMe,friends,photos,audio,video,docs,notes,pages,status,offers,questions,wall,groups,messages,notifications,stats,ads,offline&redirect_uri=http://ift.tt/1eR4tZB (на всякий случай даю полные права) и… у меня получается! ВКонтакте спрашивает разрешение, я нажимаю на «разрешить» и оно мне выдаёт токен!


После этого, для теста, пишу в адресной строке http://ift.tt/1eR4tZF! Я смог написать от имени Android!&v=5.0&access_token=тут_мой_токен. Запись на моей стене появляется.

После этого я решаю для своего удобства сделать нормальную прогу для работы с API ВКонтакте. Пишу я её на php:



<?
class Vk{

const CALLBACK_BLANK = 'http://ift.tt/1bg7R5x';
const AUTHORIZE_URL = 'http://ift.tt/1huYVMg}';
const GET_TOKEN_URL = 'http://ift.tt/1eR4ufW}';
const METHOD_URL = 'http://ift.tt/1huYTUH';

public $secret_key = null;
public $scope = array();
public $client_id = null;
public $access_token = null;
public $owner_id = 0;

function __construct($options = array()){

$this->scope[]='offline';

if(count($options) > 0){
foreach($options as $key => $value){
if($key == 'scope' && is_string($value)){
$_scope = explode(',', $value);
$this->scope = array_merge($this->scope, $_scope);
} else {
$this->$key = $value;
}

}
}
}

/**
* Выполнение вызова Api метода
* @param string $method - метод, http://ift.tt/1dK85w5
* @param array $vars - параметры метода
* @return array - выводит массив данных или ошибку (но тоже в массиве)
*/
function api($method = '', $vars = array()){

$params = http_build_query($vars);

$url = $this->http_build_query($method, $params);

return (array)$this->call($url);
}


/**
* Построение конечного URI для выхова
* @param $method
* @param string $params
* @return string
*/
private function http_build_query($method, $params = ''){
return self::METHOD_URL . $method . '?' . $params.'&access_token=' . $this->access_token;
}

/**
* Получить ссылка на запрос прав доступа
*
* @param string $type тип ответа (code - одноразовый код авторизации , token - готовый access token)
* @return mixed
*/
public function get_code_token($type="code"){

$url = self::AUTHORIZE_URL;

$scope = implode(',', $this->scope);

$url = str_replace('{client_id}', $this->client_id, $url);
$url = str_replace('{scope}', $scope, $url);
$url = str_replace('{redirect_uri}', self::CALLBACK_BLANK, $url);
$url = str_replace('{display}', 'page', $url);
$url = str_replace('{response_type}', $type, $url);

return $url;

}

public function get_token($code){

$url = self::GET_TOKEN_URL;
$url = str_replace('{code}', $code, $url);
$url = str_replace('{client_id}', $this->client_id, $url);
$url = str_replace('{client_secret}', $this->secret_key, $url);
$url = str_replace('{redirect_uri}', self::CALLBACK_BLANK, $url);

return $this->call($url);
}

function call($url = ''){

if(function_exists('curl_init')) $json = $this->curl_post($url); else $json = file_get_contents($url);

$json = json_decode($json, true);

if(isset($json['response'])) return $json['response'];

return $json;
}

// @deprecated
private function curl_get($url)
{
if(!function_exists('curl_init')) return false;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$tmp = curl_exec ($ch);
curl_close ($ch);
$tmp = preg_replace('/(?s)<meta http-equiv="Expires"[^>]*>/i', '', $tmp);
return $tmp;
}

private function curl_post($url){

if(!function_exists('curl_init')) return false;

$param = parse_url($url);

if( $curl = curl_init() ) {

curl_setopt($curl, CURLOPT_URL, $param['scheme'].'://'.$param['host'].$param['path']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $param['query']);
$out = curl_exec($curl);

curl_close($curl);

return $out;
}

return false;
}
/**
* @param array $options
*/
public function set_options($options = array()){

if(count($options) > 0){
foreach($options as $key => $value){
if($key == 'scope' && is_string($value)){
$_scope = explode(',', $value);
$this->scope = array_merge($this->scope, $_scope);
} else {
$this->$key = $value;
}

}
}

}

/**
* @param bool $gid
* @param array $files
* @return array|bool
*/
function upload_photo($gid = false, $files = array()){

if(count($files) == 0) return false;
if(!function_exists('curl_init')) return false;

$data_json = $this->api('photos.getWallUploadServer', array('gid'=> intval($gid)));

if(!isset($data_json['upload_url'])) return false;

$temp = array_chunk($files, 4);

$files = array();
$attachments = array();

foreach ($temp[0] as $key => $data) {
$files['file' . ($key+1)] = '@' . $data;
}

$upload_url = $data_json['upload_url'];

$ch = curl_init($upload_url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-type: multipart/form-data"));
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_POSTFIELDS, $files);

$upload_data = json_decode(curl_exec($ch), true);

$response = $this->api('photos.saveWallPhoto', $upload_data);

if(count($response) > 0){

foreach($response as $photo){

$attachments[] = $photo['id'];
}
}

return $attachments;

}

/**
* Заливка документа (например GIF файл)
*
* @param bool $gid
* @param $file
* @return bool|string
*/
function upload_doc($gid = false, $file){

if(!is_string($file)) return false;
if(!function_exists('curl_init')) return false;

$data_json = $this->api('docs.getUploadServer', array('gid'=> intval($gid)));

var_dump($data_json);

if(!isset($data_json['upload_url'])) return false;

$attachment = false;

$files['file'] = '@' . $file;

$upload_url = $data_json['upload_url'];

$ch = curl_init($upload_url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-type: multipart/form-data"));
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_POSTFIELDS, $files);

$upload_data = json_decode(curl_exec($ch), true);

$response = $this->api('docs.save', $upload_data);

if(count($response) > 0){

foreach($response as $photo){

$attachment = 'doc'.$photo['owner_id'].'_'.$photo['did'];
}
}

return $attachment;

}

/**
*
* Заливка видео
*
* http://ift.tt/1huYW2w
*
* @param array $options
* @param bool $file
* @return bool|string
*/
function upload_video($options = array(), $file = false){

if(!is_array($options)) return false;
if(!function_exists('curl_init')) return false;

$data_json = $this->api('video.save', $options);

if(!isset($data_json['upload_url'])) return false;

$attachment = 'video'.$data_json['owner_id'].'_'.$data_json['vid'];

$upload_url = $data_json['upload_url'];
$ch = curl_init($upload_url);

curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-type: multipart/form-data"));
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");

// если указан файл то заливаем его отправкой POST переменной video_file
if($file && file_exists($file)){

$files['video_file'] = '@' . $file;
curl_setopt($ch, CURLOPT_POSTFIELDS, $files);
curl_exec($ch);

// иначе просто обращаемся по адресу (ну надо так!)
} else {

curl_exec($ch);
}

return $attachment;

}

}

// теперь - используем наш класс:
$config['client_id'] = 2890984; //ID приложения, тут - Android
$config['user_id'] = 3087106; // id текущего пользователя (нужно не во всех случаях)
$config['access_token'] = '778eee10654727ea96fec257f4fa4b39bd23b403649265d0fe7c37c9b01bf4c25fcb66de3738d670d5bc9'; //токен, тоже нужен не всегда
//$config['scope'] = 'wall,photos,video'; // права доступа к методам (для генерации токена)

$v = new Vk($config);

$method = 'wall.postt'; ///название метода, который мы используем, в данном случае мы отпраляем запись на стену

$params = array(); //в этом массиве - параметры, которые мы отправляем
$params['message'] = 'I love Habrahabr!'; //в данном случае - только само сообщение
$response = $v->api($method, $params); //выполняем
print_r($response); //печатаем ответ, который приходит в формате JSON, но в виде массива
?>


P.S.: Это мой первый пост, поэтому прошу за недочёты не только минусовать, но и выкладывать конструктивную критику в комментариях

Спасибо за внимание!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий