Рассмотрим структуру проекта, она очень проста и состоит из 3-х файлов:
1. screen.php — точка входа в приложение.
2. classes/autoload.php — автолоадер проекта.
3. classes/Request.php — класс реализующий запросы к api яндекса.
Далее расмотрим код screen.php:
#!/usr/bin/php
<?php
require_once('classis/autoload.php');
$request = new Request();
if(isset($argv[1]) && $argv[1] == '--getToken') {
echo $request->getOauthLink();die;
}
$home = $_SERVER['HOME'];
$config = include($home . '/.config/scrphp/config.php');
$nameScreenshot = date('Y_m_d_G_i_s_') . 'screen.png';
system('scrot -s /tmp/'.$nameScreenshot);
$result = $request
->setToken($config['token'])
->setFileNameOnDisk($nameScreenshot)
->setPathToFile('/tmp/'.$nameScreenshot)
->upload()
->publicateFile();
$url = $result['public_url'];
echo $url.PHP_EOL;
Как видите это точка входа в приложение, логика проста:
1. Формирование имени скриншота
2. Вызов системной программы scrot
3. Запрос api yandex.disk и выгрузка скриншота
Файл autoload.php тоже очень прост и состоит всего из трёх строк кода, я приведу его лишь для ознакомления, и мы не будем его рассматривать подробно.
spl_autoload_register(function($name){
require_once __DIR__.'/'.$name.'.php';
});
Работа с yandex api довольно проста я написал небольшой класс Request.php, с набором неких методов, которые помогают мне в работе с ним…
<?php
class Request
{
private $_token = null;
private $_href = null;
private $_method = null;
private $_filePath = null;
private $_fileName = null;
/**
* get oauth link
*/
public function getOauthLink()
{
/**
* https://oauth.yandex.ru/authorize?
* response_type=token
* & client_id=<идентификатор приложения>
* [& device_id=<идентификатор устройства>]
* [& device_name=<имя устройства>]
* [& display=popup]
* [& login_hint=<имя пользователя или электронный адрес>]
* [& force_confirm=yes]
* [& state=<произвольная строка>]
*/
$link = 'https://oauth.yandex.ru/authorize'
.'?response_type=token'
. '&client_id=8fc231e60575439fafcdb3b9281778a3';
echo $link;
}
/**
* set file path on disk
* @param $filePath
* @return $this
*/
public function setFileNameOnDisk($name)
{
/**
* https://cloud-api.yandex.net/v1/disk/resources/upload ?
* path=<путь, по которому следует загрузить файл>
*/
$link = 'https://cloud-api.yandex.net/v1/disk/resources/upload?path='.urlencode('/'.trim($name,'/'));
$response = file_get_contents($link,false,$this->_context('GET'));
$responseAsArray = json_decode($response,true);
$this->_href = $responseAsArray['href'];
$this->_method = $responseAsArray['method'];
$this->_fileName = $name;
return $this;
}
/**
* get path to file on local disk
* @param $path
* @return $this
*/
public function setPathToFile($path) {
$this->_filePath = $path;
return $this;
}
/**
* upload file to disk
*/
public function upload()
{
$ch = curl_init($this->_href);
curl_setopt($ch,CURLOPT_HTTPHEADER,
array(
'Authorization',
'OAuth '.$this->_token
)
);
curl_setopt($ch,CURLOPT_INFILE,fopen($this->_filePath,"r"));
curl_setopt($ch,CURLOPT_INFILESIZE,filesize($this->_filePath));
curl_setopt($ch,CURLOPT_PUT,true);
curl_exec($ch);
curl_close($ch);
return $this;
}
/**
* public file and get public url for screenshot
* @return mixed
*/
public function publicateFile()
{
/**
* https://cloud-api.yandex.net/v1/disk/resources/publish ?
* path=<путь к публикуемому ресурсу>
*/
$link = 'https://cloud-api.yandex.net/v1/disk/resources/publish?path='.urlencode('/'.trim($this->_fileName,'/'));
$response = file_get_contents($link,false,$this->_context('PUT'));
$responseAsArray = json_decode($response,true);
$publicateFile = file_get_contents($responseAsArray['href'],false,$this->_context($responseAsArray['method']));
$publicateFileAsArray = json_decode($publicateFile,true);
return $publicateFileAsArray;
}
/**
* set oauth token
* @param $key
* @return $this
*/
public function setToken($token)
{
$this->_token = $token;
return $this;
}
/**
* get context for request by file_get_contents
* @param $method
* @return resource
*/
private function _context($method)
{
/**
* Authorization: OAuth <key>
*/
$opts = array(
'http'=>array(
'method'=>$method,
'header'=>"Authorization: OAuth ".$this->_token."\r\n"
)
);
$context = stream_context_create($opts);
return $context;
}
}
Рассмотрим ключевые методы данного класса для запроса методов api в основном я использовал file_get_contents, и так как мне приходилось использовать, его при запросе многих методов я написал метод генерации контекста:
/**
* get context for request by file_get_contents
* @param $method
* @return resource
*/
private function _context($method)
{
/**
* Authorization: OAuth <key>
*/
$opts = array(
'http'=>array(
'method'=>$method,
'header'=>"Authorization: OAuth ".$this->_token."\r\n"
)
);
$context = stream_context_create($opts);
return $context;
}
Он тоже довольно прост мы создаём контекст с определённом методом запроса и информации об аутентификации…
Далее нам необходимо «создать файл на yandex.disk» это действие мы производим следующим методом:
/**
* set file path on disk
* @param $filePath
* @return $this
*/
public function setFileNameOnDisk($name)
{
/**
* https://cloud-api.yandex.net/v1/disk/resources/upload ?
* path=<путь, по которому следует загрузить файл>
*/
$link = 'https://cloud-api.yandex.net/v1/disk/resources/upload?path='.urlencode('/'.trim($name,'/'));
$response = file_get_contents($link,false,$this->_context('GET'));
$responseAsArray = json_decode($response,true);
$this->_href = $responseAsArray['href'];
$this->_method = $responseAsArray['method'];
$this->_fileName = $name;
return $this;
}
Как я говорил ранее мы запрашиваем api с помощью функции file_get_contents. После того как этот метод отработает, и вся информация будет запрошена, запускаеться метод upload:
/**
* upload file to disk
*/
public function upload()
{
$ch = curl_init($this->_href);
curl_setopt($ch,CURLOPT_HTTPHEADER,
array(
'Authorization',
'OAuth '.$this->_token
)
);
curl_setopt($ch,CURLOPT_INFILE,fopen($this->_filePath,"r"));
curl_setopt($ch,CURLOPT_INFILESIZE,filesize($this->_filePath));
curl_setopt($ch,CURLOPT_PUT,true);
curl_exec($ch);
curl_close($ch);
return $this;
}
В данно случае можно было бы использовать для отправки файла одну из функций `file_get_contents` или `file_put_contents` но это не целесообразно, по причине того что пришлось бы, в контексте данных функций, в ручную имитировать заголовки и другие вытекающие из этого проблемы, так что проще просто использовать для этих целей curl.
И так файл загружен, остаёться только опубликовать его и получить прямую ссылку для просмотра, это выполняет функция publicateFile():
/**
* public file and get public url for screenshot
* @return mixed
*/
public function publicateFile()
{
/**
* https://cloud-api.yandex.net/v1/disk/resources/publish ?
* path=<путь к публикуемому ресурсу>
*/
$link = 'https://cloud-api.yandex.net/v1/disk/resources/publish?path='.urlencode('/'.trim($this->_fileName,'/'));
$response = file_get_contents($link,false,$this->_context('PUT'));
$responseAsArray = json_decode($response,true);
$publicateFile = file_get_contents($responseAsArray['href'],false,$this->_context($responseAsArray['method']));
$publicateFileAsArray = json_decode($publicateFile,true);
return $publicateFileAsArray;
}
В этом методе тоже всё довольно просто, мы запрашиваем публикацию файла методом PUT у api, яндекс возвращает ссылку, на которую мы должны выполнить запрос для подтвержедения публикации, и метод запроса в диску. И в конце концов, после второго запроса мы получаем массив который соддержит ссылку на публичный файл.
Установка скрипта
Чтож мы закончили, теперь нам предстоит придумать а как же этот скрипт будет работать из консоли? как его запустить там в «глобальной области»? ответом на эти вопросы будет phar — архив содержащий файлы php и способный выполняться как отдельное приложение, похож на тот же jar.
phar мы будем собирать с помощью утилиты box.phar для этого мы пишем простой конфигурационный файл box.json:
{
"files": [
"classis/autoload.php",
"classis/Request.php",
"screen.php"
],
"main": "screen.php",
"output": "srcphp.phar",
"stub": true
}
Для сборки запускаем:
$ php box.phar build
И наш проект готов теперь осталось только установить права на исполнение файла, и скопировать в директорию /usr/bin/srcphp:
$ chmod +x srcphp.phar
$ cp srcphp.phar /usr/bin/srcphp
Не забываем о конфигурации файла /home/myname/.config/srcphp/config.php:
<?php
// copy this file to /home/user/.config/srcphp/config.php
return array(
'token' => 'your token'
);
в token необходимо вписать полученный oAuth токен от яндекса, при переходе сгенерированной по средством запуска скрипта с ключом --getToken:
$ srcphp --getToken
Выводы
Главная мысль статьи — рассмотреть как создать консольное приложение с помощью php, на примере программы скриншотера, в следующих я буду поднимать тему использования php в различных сферах применения, и следующая статью будет посвящена разработки простого драйвера usb устройства для linux. Спасибо за внимание и доброго дня.
p.s. Исходный код приложения
Комментарии (21)
Pingvi
22.09.2016 15:22Класс Request, метод getOauthLink – у Вас в коде зашит client_id. Неплохо бы его вынести в конфигурационный файл.
lnroma
22.09.2016 15:35-1Из начально было так, но из за соображения того что есть лишний какой то пункт в конфиге, который навряд ли кто то будет заполнять, я решил его просто прописать(хотя да некий хардкод, но он не так принципиален).
Pingvi
22.09.2016 19:00По Вашей логике и токен не нужен в конфигурационном файле. Вообще печально, что Вы так думаете…
lnroma
22.09.2016 19:09Токен как раз в конфигурации находиться, так как он меняется от клиента к клиенту, я опять повторяюсь вынести можно и может даже нужно. Но логика была проста скачав проэкт, пользователь получает токен пишет его в конфиг и копирует конфиг в домашнюю директорию, в другом случае пользователю надо сначала скопировать только потом получить токен, и позже вставить его в конфиг. Что добавляет ещё один не очевидный шаг для инсталяции.
barker
22.09.2016 16:57+1Многие люди считают что php подходит только для разработки сайтиков, и никак не может быть использован, в других областях применения языков программирования, для создания программ
Ну, вообще-то между «подходит» и «может быть использован» огромная пропасть для адекватных людей, стоящих перед выбором инструмента. Любой язык (с несущественными оговорками) можно использовать для решения любой задачи. А вот «подходит» — это отдельный разговор совсем.
KorP
22.09.2016 17:04Вы мне только объясните пожалуйста — а почему был выбран именно php? Тем более что для непосредственного снятия скриншота вообще системная утилита вызывается… или вы либу для работы с апи яндекса только под php нашли?
Я не против php, у меня у самого проект на нём написан, ибо когда начинал, кроме него вообще ничего не мог и не знал, а сейчас переписывать всё это уже не решусь, по этому мне просто интересны аргументы выбора языка, ибо скрипт из 20 строк такой, можно написать на любом ЯП с помощью гугла за 15 минут…lnroma
22.09.2016 17:12Можно и какой то подойдёт безусловно лучше чем php. Но в этом то и суть моей статьи что всё таки php можно рассматривать и с точки зрения «системного программирования» (тут я имею ввиду системных скриптов и приложений) а не только как ЯП для создания сайтов.
lexore
22.09.2016 18:22+5Мне вспоминается притча Мастер Фу и десять тысяч строк кода.
На bash это можно сделать вот так:
AUTH='USER:PASSWORD' FILE=$(date +'%Y_%m_%d_%H_%M_%S_screen.png') XML='<propertyupdate xmlns="DAV:"><set><prop><public_url xmlns="urn:yandex:disk:meta">true</public_url></prop></set></propertyupdate>' scrot -s "/tmp/$FILE" curl -s --user "$AUTH" -T "/tmp/$FILE" -X PUT "https://webdav.yandex.ru/" curl -s --user "$AUTH" -d "$XML" -X PROPPATCH "https://webdav.yandex.ru/$FILE" | grep -Eo 'https://yadi.sk/[^<]+' rm -f "/tmp/$FILE"
lnroma
22.09.2016 18:52Да и он даже больше для этого подходит. Но цель статьи показать на живом примере, что php можно так же использовать для пользовательских скриптов.
lexore
22.09.2016 19:23Конечно, можно. Но зачем? Позвольте, пояснить свою мысль.
Многие люди считают что php подходит только для разработки сайтиков, и никак не может быть использован, в других областях применения языков программирования, для создания программ…
Я думаю, те самые "многие люди", сами того не желая, ввели вас в заблуждение, говоря так категорично.
Имхо, на любом языке программирования можно решить любую задачу, которую обычно решают на другом языке. Язык программирования — это просто набор инструкций, команд, которые нужно выполнить. Естественно, составить инструкцию можно на любом языке.
Просто обычно смотрят на себестоимость решения задачи на том или ином языке. Под себестоимостью я имею в виду удобство, скорость и простоту написания кода, получившийся объем этого кода. Сюда же можно добавить и скорость выполнения написанного кода.
На php можно написать что угодно, но во что это обойдется…
Демон на php может упасть или начать есть память — нужно написать кучу оберток и следить за этим (и перезапускать его). Для драйверов код на php слишком медленный и жрущий память. Для системных скриптов — слишком много кода на единицу функциональности.
В итоге, если использовать язык вне его ниши, можно больше времени тратить на борьбу с языком и изобретение костылесипедов, чем на создание функциональности. Я думаю, именно это и имели в виду те люди, говоря "никак не может быть использован". Это как "нельзя тыкать шпилькой в розетку" — тыкать-то можно, просто результат вам не понравится.zzzmmtt
23.09.2016 09:44Плюс шелл-скрипты — набор системных команд, как правило. А PHP-скрипт — лишний слой, которому ещё и интерпретатор нужен (память, время на обработку), перед тем, как запустить системные же команды/приложения. Можно использовать php таким образом, но нет смысла. На вопрос «Зачем?» ответ может быть в данном случае только один — «Потому что можем».
onotole5g
23.09.2016 10:47По-моему, для таких системых задач, как создание скриншотов и выгрузки их на Яндекс.Диск — php идеален.
kalobyte
23.09.2016 02:12вот я тоже читал и думал: может быть автор круче меня и у него есть какая-то фишка, которая делает пхп круче для решения этой задачи, нежели баш + курл
я как бы не программист и стараюсь не вякать в таких темах и послушать умных людей или сам автор может быть умный, ведь все программисты умные и все такое…
б-же, никак не избавлюсь от этого комплекса неполноценности, но твой пост дает мне надежду на это
оказывается не все программисты умные и наверное я не хуже крутых программистов даже (только почему я не умею писать код?)
вобщем если бы мне надо было залить картинку на яндыкс, я бы заюзал баш и курл
сейчас мне надо брать гугл календарь за неделю и постить записи в пейсбук
для этого у гугла ессть целая пхп библиотека аж на 25 мегабайт и она даже работает
для получения списка записей в календаре надо просто сделать гет запрос
GET https://www.googleapis.com/calendar/v3/calendars/ид-календаря/events?key={YOUR_API_KEY}
получаем
200 OK
— Show headers —
{
«kind»: «calendar#events»,
«etag»: "\«p338ejguvkehcu0g\»",
«summary»: «kalobyte»,
«updated»: «2016-09-22T05:50:07.906Z»,
«timeZone»: «Europe/Berlin»,
«accessRole»: «owner»,
«defaultReminders»: [
{
«method»: «popup»,
«minutes»: 30
}
],
«nextSyncToken»: «CNDpw9-jos8CENDpw9-jos8CGAU=»,
«items»: [
{
«kind»: «calendar#event»,
«etag»: "\«2946678667918000\»",
«id»: «6uvg9e46eaaenh7hnctojt2d9o»,
«status»: «confirmed»,
«htmlLink»: «https://www.google.com/calendar/event?eid=NnV2ZzllNDZlYWFlbmg3aG5jdG9qdDJkOW8ga2Fsb2J5dGVAaW5ib3gucnU»,
«created»: «2016-09-08T12:55:33.000Z»,
«updated»: «2016-09-08T12:55:33.959Z»,
«summary»: «aaa»,
«creator»: {
«email»: "",
«self»: true
},
«organizer»: {
«email»: "",
«self»: true
},
«start»: {
«dateTime»: «2016-09-08T09:30:00+02:00»
},
«end»: {
«dateTime»: «2016-09-08T15:00:00+02:00»
},
«iCalUID»: «6uvg9e46eaaenh7hnctojt2d9o@google.com»,
«sequence»: 0,
«guestsCanInviteOthers»: false,
«guestsCanSeeOtherGuests»: false,
«reminders»: {
«useDefault»: true
}
}
]
}
проще поди было бы вызвать гет запрос обычной пхп функцией, там ведь должно что-то быть
потом распарсить джейсона и готовы данные
но в пхп библиотеке надо для этого еще кучу всего написать и почему-то нет автодополнения синтаксиса методов и полей из этой библиотеки в пхп шторме
на ковыряние и работу я убил 2 недели, потому что мануал библиотеки и примеров не соответствовал апи и вываливалась ошибка 404 и что там не найдено — непонятно и мануал молчит
чисто интуитивно поменял ид календаря с primary на конкретный ид и все заработало
теперь мне осталось разобраться с апи пейсбука и может быть там тоже есть библиотека пхпlnroma
23.09.2016 11:02Ну если это GET запрос можно обойтись одной строчкой на php
$result = json_decode(file_get_contents('https://www.googleapis.com/calendar/v3/calendars/ид-календаря/events?key={YOUR_API_KEY}'));
Но вот на bash это уже сложнее http://stackoverflow.com/questions/1955505/parsing-json-with-unix-tools.
POPSuL
23.09.2016 08:15Я к середине статьи тоже задумался о том, мол «блин, да это же можно сделать скриптом из 10 строк на баше, причем тут PHP?».
На PHP, в общем-то, можно и GUI-приложения лепить, но зачем?
PaulZi
А ещё есть функция gd `imagegrabscreen()`, что позволит избавиться от одного системного вызова.
Согласен с автором — PHP давно перестал течь, а с 7 версии и производительность подтянули, так что вполне успешно может использоваться вне рамок веба.
Delphinum
imagegrabscreen только для Windows
lnroma
Сейчас протестирую на linux, да она не работает в linux. Но он захватывает весь экран я так понимаю. А scrot даёт выбрать область захвата мышкой.
Delphinum
Так в манах черным по белому:
Note:
This function is only available on Windows