Я пытался проснуться рано утром. Проблема в том, что будильник разбудил всех, а не только меня. Чтобы обойти эту проблему, я недавно купил самый дешевый Fitbit, который я смог найти, узнав, что у них аккуратная тихая тревога.
Правда в том, что если бы у меня были деньги, я бы предпочел купить часы Apple. Когда я получил Fitbit, мой мозг программиста сразу же перешел к вопросу; «Как я могу взломать эту вещь?»
В итоге я немного узнал о Fitbit, OAuth и Twitter API. Я также узнал, что иногда лучше просто купить часы Apple …
Код для этого урока можно найти на Github .
Начиная
Я решил попробовать использовать тихую сигнализацию в качестве системы уведомлений. Я бы проверил наличие новых сообщений в Твиттере и установил будильник как можно быстрее. Таким образом, когда я почувствовал тихую тревогу, которую я не ожидал, я мог проверить Twitter …
Оказывается, и Fitbit, и Twitter имеют JSON API, и оба проходят аутентификацию с помощью OAuth. Я начал с создания нового приложения Lumen , которое будет служить планировщиком задач и коммуникатором для этого проекта:
composer install laravel/lumen .
Lumen — это просто упрощенная версия фреймворка Laravel . Есть несколько вещей, которые по умолчанию отключены, а другие настроены на «легкие» альтернативные реализации. Все, что вы строите в Lumen, может быть перенесено в Laravel, поэтому я подумал, что попробую это и посмотрю, достаточно ли он быстр и функционален для связи.
Связь должна происходить в два этапа:
- Создать ссылки / кнопки для подключения приложения к Twitter и Fitbit
- Запланируйте задачи командной строки, чтобы проверить наличие новых прямых сообщений и установить сигналы тревоги.
Я начал с добавления маршрутов для первого шага:
$app->get("/", function() { return view("dashboard"); }); $app->get("/auth/fitbit", function() { // begin auth with fitbit }); $app->get("/auth/fitbit/callback", function() { // store oauth credentials }); $app->get("/auth/twitter", function() { // begin auth with twitter }); $app->get("/auth/twitter/callback", function() { // store oauth credentials });
Это происходит в app/Http/routes.php
.
Я также должен был создать представление панели инструментов для них:
<a href="{{ url("auth/fitbit") }}">connect with fitbit</a> <a href="{{ url("auth/twitter") }}">connect with twitter</a>
Это происходит в resources/views/dashboard.blade.php
.
Команда artisan serve
была удалена из Lumen, но если вы действительно хотите вернуть ее (как я), вы можете установить ее с помощью:
composer require mlntn/lumen-artisan-serve
Вам также необходимо добавить \Mlntn\Console\Commands\Serve::class
в список зарегистрированных консольных команд, в app/Console/Kernel.php
.
Регистрация приложений
И Fitbit, и Twitter требовали, чтобы я зарегистрировал новые приложения, прежде чем они предоставят ключи OAuth, необходимые для аутентификации.
Я разрабатывал все это локально, используя сервер Artisan, чтобы добраться до таких маршрутов, как http://localhost:8080/auth/twitter
. Страница регистрации приложения Twitter не разрешает URL-адреса обратного вызова с локальным хостом, IP-адресом или номерами портов.
Я собирался настроить перенаправление с https://assertchris.io/auth/twitter
на http://localhost:8080/auth/twitter
когда заметил, что вы можете использовать разные URL-адреса обратного вызова в интерфейсе Twitter и запросах OAuth. Если вы столкнулись с этой же проблемой, просто используйте поддельный URL-адрес (из реального домена) в интерфейсе Twitter и локальный URL-адрес при выполнении запросов OAuth, которые я собираюсь показать вам…
Выполнение запросов OAuth
Одна из причин, по которой я выбрал Lumen, была из-за Socialite . Socialite абстрагирует большую часть тяжелой работы, связанной с OAuth, а Guzzle абстрагирует остальных!
Я добавил открытый и секретный ключи для Fitbit и Twitter:
FITBIT_KEY=... FITBIT_SECRET=... FITBIT_REDIRECT_URI=... TWITTER_KEY=... TWITTER_SECRET=... TWITTER_REDIRECT_URI=...
Это происходит в .env
.
Если бы я .env
это приложение в Laravel, мне также нужно было бы изменить app/config/services.php
чтобы ссылаться на эти переменные .env
.
Затем я установил Socialite и провайдеров Fitbit и Twitter с https://socialiteproviders.github.io
:
composer require laravel/socialite composer require socialiteproviders/fitbit composer require socialiteproviders/twitter
В части инструкций по установке Socialite рекомендуется добавить поставщика услуг Socialite в список поставщиков услуг в config/app.php
. Сторонние поставщики Fitbit и Twitter имеют другого поставщика услуг, который заменяет официального.
Lumen покончил с папкой config
, поэтому этот поставщик услуг должен быть зарегистрирован в bootstrap/app.php
. Я также раскомментировал класс EventServiceProvider
:
$app->register( App\Providers\EventServiceProvider::class ); $app->register( SocialiteProviders\Manager\ServiceProvider::class );
Это происходит в bootstrap/app.php
.
Теперь, EventServiceProvider
класс EventServiceProvider
вернулся в игру, я мог добавить события, которые требовались этим поставщикам OAuth:
namespace App\Providers; use Laravel\Lumen\Providers\EventServiceProvider as Base; use SocialiteProviders\Manager\SocialiteWasCalled; use SocialiteProviders\Fitbit\FitbitExtendSocialite; use SocialiteProviders\Twitter\TwitterExtendSocialite; class EventServiceProvider extends Base { protected $listen = [ SocialiteWasCalled::class => [ FitbitExtendSocialite::class . "@handle", TwitterExtendSocialite::class . "@handle", ], ]; }
Это происходит в app/Providers/EventServiceProvider.php
.
Со всеми этими настройками я мог начать подключаться к Fitbit и Twitter:
use Laravel\Socialite\Contracts\Factory; $manager = $app->make(Factory::class); $fitbit = $manager->with("fitbit")->stateless(); $twitter = $manager->with("twitter"); $app->get("/auth/fitbit", function() use ($fitbit) { return $fitbit->redirect(); }); $app->get("/auth/fitbit/callback", function() use ($fitbit) { // use $fitbit->user() }); $app->get("/auth/twitter", function() use ($twitter) { return $twitter->redirect(); }); $app->get("/auth/twitter/callback", function() use ($twitter) { // use $twitter->user() });
Это происходит в app/Http/routes.php
.
Еще одно, что по умолчанию отключено в Lumen, это управление сессиями. Это имеет смысл, поскольку Lumen предназначен в основном для веб-служб JSON без сохранения состояния, а не для реальных веб-сайтов. Проблема в том, что Socialite использует сессии по умолчанию для OAuth 2. Я могу отключить это с помощью метода stateless()
…
Каждый провайдер Socialite предоставляет метод redirect()
, который я могу вернуть по собственному маршруту. Когда пользователи попадают на этот маршрут, они перенаправляются в службу, с которой я хочу, чтобы они проходили аутентификацию.
Простые dd($fitbit->user())
и dd($twitter->user())
показывают мне, что данные возвращаются на мой локальный сервер, как и ожидалось.
Теперь я могу получать прямые сообщения из Twitter:
use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Subscriber\Oauth\Oauth1; use Illuminate\Support\Facades\Cache; $app->get("/auth/twitter/callback", function() use ($twitter) { $user = $twitter->user(); Cache::forever("TWITTER_TOKEN", $user->token); Cache::forever("TWITTER_TOKEN_SECRET", $user->tokenSecret); return redirect("/"); });
Это происходит в app/Http/routes.php
.
Подключение к этим услугам — только первый шаг. Как только они отправят данные обратно в мое приложение, мне нужно прикрепить его к будущим запросам. Лучше получать прямые сообщения по другому маршруту, поэтому сейчас я просто собираюсь хранить их в кеше.
Поставщик кэша по умолчанию — Memcache. Вы можете установить его, если он отсутствует в вашей ОС, или настроить другой драйвер кеша.
Я также использую фасад Laravel, чтобы получить быстрый доступ к базовым классам кэша. По умолчанию они отключены в Lumen, но вы можете раскомментировать $app->withFacades();
в boostrap/app.php
чтобы включить их снова.
Извлечение прямых сообщений
Далее я добавлю ссылку на панель инструментов и новый маршрут для получения прямых сообщений:
<a href="{{ url("twitter/fetch") }}">fetch direct messages</a>
Это происходит в resources/views/dashboard.blade.php
.
$app->get("twitter/fetch", function() { $middleware = new Oauth1([ "consumer_key" => getenv("TWITTER_KEY"), "consumer_secret" => getenv("TWITTER_SECRET"), "token" => Cache::get("TWITTER_TOKEN"), "token_secret" => Cache::get("TWITTER_TOKEN_SECRET"), ]); $stack = HandlerStack::create(); $stack->push($middleware); $client = new Client([ "base_uri" => "https://api.twitter.com/1.1/", "handler" => $stack, ]); $response = $client->get("direct_messages.json", [ "auth" => "oauth", ]); header("content-type: application/json"); print $response->getBody(); });
Это происходит в app/Http/routes.php
.
Поскольку у меня есть токен и секрет токена (отправленный нам из Twitter как часть процесса OAuth), мы можем использовать HandlerStack
и Oauth1
для прозрачного добавления учетных данных OAuth к запросам, которые я делаю с помощью Guzzle.
Я могу немного изменить это, чтобы записывать текущие прямые сообщения, чтобы я мог знать, когда я получил что-то новое:
$latest = Cache::get("TWITTER_LATEST", 0); $response = $client->get("direct_messages.json", [ "auth" => "oauth", "query" => [ "since_id" => $latest, ], ]); // header("content-type: application/json"); // print $response->getBody(); $response = (string) $response->getBody(); $response = json_decode($response, true); foreach ($response as $message) { if ($message["id"] > $latest) { $latest = $message["id"]; } } Cache::forever("TWITTER_LATEST", $latest);
Это происходит в app/Http/routes.php
.
Twitter позволяет мне отфильтровывать все прямые сообщения, начиная с указанного идентификатора прямого сообщения, поэтому идея состоит в том, чтобы я перебирал все прямые сообщения и кэшировал самый новый (самый большой) идентификатор. Тогда будущие запросы не будут включать старые прямые сообщения, и я могу установить будильник, если появятся новые сообщения.
Установка тихих будильников
Я хочу сохранить данные токена Fitbit OAuth так же, как я делал для Twitter:
$app->get("/auth/fitbit/callback", function() use ($fitbit) { $user = $fitbit->user(); Cache::forever("FITBIT_TOKEN", $user->token); Cache::forever("FITBIT_USER_ID", $user->id); return redirect("/"); });
Это происходит в app/Http/routes.php
.
Теперь, предполагая, что я подключился к Fitbit и Twitter, я могу устанавливать сигналы при появлении новых прямых сообщений:
if (count($response)) { $token = Cache::get("FITBIT_TOKEN"); $userId = Cache::get("FITBIT_USER_ID"); $client = new Client([ "base_uri" => "https://api.fitbit.com/1/", ]); $response = $client->get("user/-/devices.json", [ "headers" => [ "Authorization" => "Bearer {$token}" ] ]); $response = (string) $response->getBody(); $response = json_decode($response, true); $id = $response[0]["id"]; $endpoint = "user/{$userId}/devices/tracker/{$id}/alarms.json"; $response = $client->post($endpoint, [ "headers" => [ "Authorization" => "Bearer {$token}" ], "form_params" => [ "time" => date("H:iP", strtotime("+1 minute")), "enabled" => "true", "recurring" => "false", "weekDays" => [strtoupper(date("l"))], ], ]); header("content-type: application/json"); print $response->getBody(); }
Это происходит в app/Http/routes.php
.
Обратите внимание, как мне нужно использовать другой тип аутентификации в Fitbit? Fitbit использует OAuth 2 и разрешает заголовки Bearer для токена. Это намного проще, чем требования OAuth 1 в Twitter.
Первый запрос, который я делаю, — это выбор устройств, которые у меня есть в моей учетной записи Fitbit. Если бы у меня было немного больше времени, я мог бы создать список и какой-то выбор, чтобы он был более динамичным.
Получив идентификатор устройства (назначенный Fitbit), я могу создать новый тихий сигнал тревоги. Это требует специального формата даты, и день должен быть таким, какой когда-либо сегодня, в верхнем регистре. Этот бит отладочной информации говорит мне, был ли установлен новый сигнал тревоги или нет. Если нет прямых сообщений, я ничего не вижу.
Вывод
Это был действительно веселый проект для меня. Я немного узнал об ограничениях, накладываемых на новые приложения Lumen, подключение к сервисам OAuth с помощью Socialite и о том, как взаимодействовать с API Fitbit и Twitter.
Мой Fitbit синхронизируется каждые 15 минут (как минимум), а также каждый раз, когда я открываю приложение для iPhone. Возможно, более новые модели Fitbit синхронизируются чаще или имеют какой-то толчок, инициируемый сервером. Это просто ограничение, с которым мне придется жить. Или я мог бы купить Apple Watch.