Статьи

Создание клиента Twitter с NodeJS и Angular

В этом руководстве мы рассмотрим, как создать базовый клиент Twitter с NodeJS и приложение Angular для отображения домашней хроники твитов. Это быстрый обзор, на который нужно обратить внимание при настройке собственного клиента Twitter и приложения Angular.

Во-первых, мы собираемся создать сервер NodeJS, который будет обрабатывать обмен данными между API Twitter и приложением Angular. Затем мы создадим приложение Angular для отображения и взаимодействия с вашей временной шкалой Twitter.

Хотя вы, возможно, сможете пройти этот урок без какого-либо предварительного опыта работы с NodeJS или Angular, я предполагаю некоторые предварительные знания в этой статье.

Настройка проекта

Вам понадобится последняя версия NodeJS, установленная на вашем компьютере. Затем убедитесь, что у вас есть угловой CLI . Предоставленные ссылки дают вам хорошее место для начала, если вам нужна помощь в решении любой из этих задач.

Исходный код проекта можно найти на GitHub . Вы можете запустить все это локально, либо клонировав его с помощью Git, либо загрузив файлы из архива GitHub хранилища .

git clone https://github.com/sitepoint-editors/twitter-angular-client 

Когда у вас есть файлы, вам нужно запустить npm install чтобы получить все установленные зависимости. Тогда мы можем приступить к работе!

Создание клиента Twitter в NodeJS

Чтобы получить доступ к API Twitter, нам нужно зарегистрироваться для нового «приложения», которое, по сути, является для Twitter способом предоставить нам набор учетных данных. Они уникальны для вашего приложения, поэтому никому не делитесь ими публично. Конечно, вы должны иметь учетную запись Twitter для доступа к данным.

Для начала перейдите на https://apps.twitter.com/ и выберите « Создать новое приложение» . Вы можете указать название, описание и URL-адрес веб-сайта своего приложения. (Вы можете использовать поддельный URL на данный момент. Если вы публикуете свое приложение, это должен быть ваш настоящий веб-сайт.)

Оттуда вы увидите новую страницу приложения с вашими данными. Перейдите на страницу « Ключи и токены доступа» , где внизу находится кнопка « Создать мой токен доступа» . Нажмите кнопку, и вы увидите четыре значения: Ключ потребителя ( ключ API), Секрет потребителя (Секрет API), Маркер доступа и Секрет токена доступа . Мы воспользуемся ими в ближайшее время, поэтому обязательно держите эту информацию под рукой.

Создание клиента Twitter в NodeJS

Теперь пришло время покопаться в нашем сервере NodeJS, который сократит разрыв между API Twitter и приложением Angular. В проекте вы должны увидеть файл server.js , который вам нужно будет открыть и настроить.

Во-первых, вам нужно обновить блок, содержащий учетные данные, которые вы получили из приложения Twitter ранее. Вы должны скопировать эти значения в блок здесь. Мы используем пакет Twitter под названием Twit, чтобы помочь нам подключиться к Twitter, хотя есть и другие с различными уровнями функциональности.

 const client = new Twitter({ consumer_key: 'CONSUMER_KEY', consumer_secret: 'CONSUMER_SECRET', access_token: 'ACCESS_TOKEN', access_token_secret: 'ACCESS_TOKEN_SECRET' }); 

Теперь мы должны иметь возможность подключиться к Twitter. Мы также используем популярный ExpressJS для создания и управления нашим сервером. Теперь, когда у вас есть установленные учетные данные, вы можете запустить сервер.

 node server 

Нашим следующим шагом будет создание нескольких маршрутов, которые будут обрабатывать HTTP-запросы, которые наше приложение Angular должно будет выполнить для загрузки данных Twitter. Наш первый путь — получить текущего пользователя и проверить его учетные данные. Предоставленные вами токен доступа и секретный ключ связаны с вашей учетной записью Twitter, поэтому в этом случае вы будете авторизованным пользователем. Когда вызывается этот маршрут, он account/verify_credentials конечную точку account/verify_credentials Twitter account/verify_credentials и возвращает объект, содержащий ваши пользовательские данные.

 app.get('/api/user', (req, res) => { client.get('account/verify_credentials').then(user => { res.send(user) }).catch(error => { res.send(error); }); }); 

Следующий маршрут, который мы создадим, — это получить ваш домашний график. Он запрашивает statuses/home_timeline точку statuses/home_timeline и передает несколько параметров, чтобы предоставить нам больше данных, которые нам нужны.

Из-за ограничения скорости в API Twitter мы внедрили простой кеш, который будет запрашивать новые данные только раз в минуту (это максимальная скорость перед тем, как вы получите ошибки). Он в основном отслеживает последний ответ и время, когда он был запрошен, позволяя только новым запросам в Twitter запускаться через минуту. Ограничение скорости является основным соображением дизайна при создании приложения для Twitter.

 let cache = []; let cacheAge = 0; app.get('/api/home', (req, res) => { if (Date.now() - cacheAge > 60000) { cacheAge = Date.now(); const params = { tweet_mode: 'extended', count: 200 }; if (req.query.since) { params.since_id = req.query.since; } client .get(`statuses/home_timeline`, params) .then(timeline => { cache = timeline; res.send(timeline); }) .catch(error => res.send(error)); } else { res.send(cache); } }); 

Наконец, мы создаем набор маршрутов для обработки действий типа «нравится / не похож» и «ретвитнуть / удалить» для твита. Это позволит нам не только читать данные, но и принимать меры. Для этого потребуется установить уровень доступа к приложению для чтения и записи (в случае, если вы изменили его в настройках приложения Twitter).

 app.post('/api/favorite/:id', (req, res) => { const path = (req.body.state) ? 'create' : 'destroy'; client .post(`favorites/${path}`, {id: req.params.id}) .then(tweet => res.send(tweet)) .catch(error => res.send(error)); }); app.post('/api/retweet/:id', (req, res) => { const path = (req.body.state) ? 'retweet' : 'unretweet'; client .post(`statuses/retweet/${req.params.id}`) .then(tweet => res.send(tweet)) .catch(error => res.send(error)); }); 

Существует много API-интерфейсов Twitter для работы с данными Twitter, но основные правила остаются теми же. Единственная серьезная проблема здесь заключается в том, что у нас есть жестко запрограммированные учетные данные для одного пользователя, которые вам понадобятся для того, чтобы настроить свой собственный сервер OAuth (или использовать существующий) для обработки аспектов аутентификации, которые вы можете узнать больше. о документации по аутентификации в Twitter .

Создание углового приложения

Теперь пришло время обратить наше внимание на приложение Angular, которое использует созданный нами сервер. Мы рассмотрим ключевые аспекты приложения и то, как они работают для создания конечного результата. Мы создали это приложение, используя Clarity для пользовательского интерфейса (оно дает нам много полезных компонентов макета), но в остальном все просто Angular.

Чтобы запустить приложение Angular, просто запустите следующую команду и откройте http: // localhost: 4200:

 ng serve 

Внутри приложения у нас есть модель в src/app/tweet.ts которая содержит интерфейс TypeScript, который описывает большинство свойств твита (некоторые были опущены). Я считаю, что важно правильно описать ваши типы для крупномасштабных приложений Angular, а также для небольших приложений, поэтому этот интерфейс дает нам форму твита.

Угловой TwitterService

Во-первых, нам нужен сервис, который может отправлять запросы на наш сервер NodeJS для получения последних твитов. В Angular HttpClient — это утилита, которую вы используете для выполнения HTTP-запросов, поэтому я создал службу Angular для инкапсуляции логики этих вызовов. Откройте src/app/twitter.service.ts и вы увидите следующий код:

 import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '../environments/environment'; import { Tweet } from './tweet'; export interface TwitterResponse { data: any; resp: any; } @Injectable() export class TwitterService { constructor(private http: HttpClient) { } user() { return this.http.get<TwitterResponse>(`${environment.api}/user`); } home(since?: string) { return this.http.get<TwitterResponse>(`${environment.api}/home?since=${since}`); } action(property: 'favorite'|'retweet', id: string, state: boolean) { return this.http.post<TwitterResponse>(`${environment.api}/${property}/${id}`, {state}); } } 

Это довольно простой сервис, в котором есть методы для создания запроса для каждого API, который мы будем поддерживать. user метод вернет текущего пользователя (который всегда будет вами). home метод вернет последние 200 твитов в вашу домашнюю временную шкалу (или сколько их появилось с момента последнего указанного твита). Наконец, свойство action обрабатывает выполнение избранного или ретвита, отправляя логическое значение state для переключения состояния.

Этот сервис является общим, и каждый из этих методов возвращает Observable. Если вы хотите узнать о них больше, вы можете прочитать о Functional Reactive с RXJS , но способ их использования здесь аналогичен тому, как работает обещание. Мы увидим, как использовать их в ближайшее время.

Использование Angular TwitterService для загрузки пользователя

Мы будем использовать TwitterService в нескольких местах, начиная с загрузки AppComponent. Мы будем использовать его для загрузки информации о пользователе (которая отображается в верхнем углу) и для загрузки списка твитов для домашней страницы. Откройте src/app/app.component.ts и вы должны увидеть следующий код:

 import { Component , OnInit } from '@angular/core'; import { TwitterService } from './twitter.service'; import { Tweet } from './tweet'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], providers: [TwitterService] }) export class AppComponent implements OnInit { user; constructor(private twitter: TwitterService) {} ngOnInit() { this.twitter.user().subscribe(user => this.user = user.data); } } 

AppComponent делает одно главное, используя наш сервис Twitter. Метод ngOnInit срабатывает, как только компонент инициализирован, и запрашивает пользовательские данные. Здесь мы используем Observable, возвращаемый методом TwitterService.user , и когда мы используем subscribe он будет запускать фактический HTTP-запрос. После возврата функция обратного вызова сохраняет свойство пользователя, которое используется для отображения содержимого на панели навигации. Вы можете увидеть привязки пользовательских свойств в шаблоне компонента ниже, например, user.profile_image_url_https :

 <clr-main-container> <clr-header class="header-4"> <div class="branding"> <a class="nav-link"> <div class="title">Twangular</div> </a> </div> <div class="header-actions" *ngIf="user"> <a class="nav-link"> <span class="nav-text"> <img [src]="user.profile_image_url_https" class="avatar" /> @{{user.screen_name}} </span> </a> </div> </clr-header> <div class="content-container"> <main class="content-area"> <app-tweets></app-tweets> </main> </div> </clr-main-container> 

Кроме того, использование <app-tweets></app-tweets> вставит TweetsComponent, который обрабатывает фактическую загрузку и отображение твитов, поэтому давайте посмотрим на это сейчас.

Отображение списка твитов

Чтобы помочь отделить нашу логику, у нас фактически есть два компонента для отображения списка твитов. TweetsComponent управляет списком твитов, а также обрабатывает запросы к нашему сервису NodeJS на лайк или ретвит твита. Затем TweetComponent используется для отображения фактического форматирования и отображения твита. Я всегда рекомендую пытаться разделить компоненты на отдельные роли, и в этом случае TweetsComponent отвечает за взаимодействие с данными, например, загрузку и ретвит, а TweetComponent не знает о загрузке данных, а только отображает контент. Начнем с рассмотрения TweetsComponent, поэтому ниже приводится содержимое src/app/tweets/tweets.component.ts :

 import { Component, OnInit, Input, OnDestroy } from '@angular/core'; import { Tweet } from '../tweet'; import { TwitterService } from '../twitter.service'; @Component({ selector: 'app-tweets', templateUrl: './tweets.component.html', styleUrls: ['./tweets.component.scss'] }) export class TweetsComponent implements OnInit, OnDestroy { inflight = false; tweets: Tweet[] = []; ids = []; timer; since = ''; constructor(private twitter: TwitterService) {} ngOnInit() { this.getTweets(); this.timer = setInterval(() => this.getTweets(), 61000); } ngOnDestroy() { if (this.timer) { clearInterval(this.timer); } } getTweets() { this.twitter.home(this.since).subscribe(tweets => { tweets.data.reverse().forEach(tweet => { if (this.ids.indexOf(tweet.id_str) < 0) { this.ids.push(tweet.id_str); this.tweets.unshift(tweet); } }); this.since = this.tweets[0].id_str; this.cleanUp(); }); } cleanUp() { if (this.tweets.length > 1000) { this.tweets.splice(1000); this.ids.splice(1000); } } action(action, index) { if (this.inflight) { return; } const stateKey = (action.property === 'favorite') ? 'favorited' : 'retweeted'; const newState = !action.tweet[stateKey]; this.inflight = true; this.twitter.action(action.property, action.tweet.id_str, newState).subscribe(tweet => { this.tweets[index][stateKey] = newState; this.tweets[index][action.property + '_count'] += (newState) ? 1 : -1; this.inflight = false; }); } } 

Этот компонент берет на себя роль обработки всей загрузки и взаимодействия со списком твитов. В методе ngOnInit мы вызываем метод для получения твитов, а также устанавливаем интервал, который перезагружает последние твиты каждые 61 секунду. Помните, что существует ограничение по количеству запросов, которое мы можем сделать, поэтому это помогает нам не превышать это ограничение. Метод ngOnDestroy просто сбрасывает таймер при удалении компонента, что является хорошей практикой, всегда предотвращающей утечки памяти.

Затем у нас есть метод getTweets , который использует getTweets для запроса домашней шкалы времени. Он также передает строку, содержащую последний полученный твит, поэтому мы можем запросить только твиты с момента создания этого идентификатора. Когда мы подписываемся, делается запрос, и обратный вызов дает нам список твитов. Поскольку мы хотим сначала показать самые последние твиты, мы переворачиваем массив, а затем помещаем их в существующий список твитов, обновляем последнюю ссылку на идентификатор твита, а затем делаем некоторую очистку. Если у нас более 1000 наименований, мы отбрасываем остаток, чтобы помочь контролировать потребление памяти.

Важно отметить, что мы используем свойство id_str из твитов. Это связано с тем, что JavaScript (и впоследствии JSON) не может точно обрабатывать числа свыше 53 бит (или, другими словами, JavaScript не может обрабатывать очень большие числа, см. Идентификаторы снежинок ).

Метод action будет использоваться для обработки вызова службы TwitterService для добавления в избранное или ретвита твита. Он выполняет действие (избранное или ретвит), а затем переключает состояние свойства. (Если вы ранее сделали ретвит, это было бы, например, без ретвита). Твит содержит метаданные о том, добавили ли вы в избранное или ретвитнули, а также подсчет количества фаворитов или ретвитов. Поскольку ваше действие добавления или ретвита изменяет это состояние, этот метод также соответствующим образом обновляет значения твита.

Шаблон для компонента можно найти по адресу src/app/tweets/tweets.component.html и показан ниже. Это довольно просто, поскольку он перебирает список твитов и отображает экземпляр TweetComponent для каждого твита. Если твит является ретвитом, он также связывает статус ретвита. Twitter добавляет свойство retweeted_status с данными исходного твита, если это ретвит, и если это то, что мы действительно хотим отобразить. Поскольку мы хотим отобразить статус ретвита, он фактически заменяет реальный твит, когда он присутствует.

 <div class="tweets"> <div class="card" *ngFor="let tweet of tweets; let i = index"> <app-tweet *ngIf="tweet.retweeted_status" [tweet]="tweet.retweeted_status" [retweet]="tweet" (action)="action($event, i)"></app-tweet> <app-tweet *ngIf="!tweet.retweeted_status" [tweet]="tweet" (action)="action($event, i)"></app-tweet> </div> </div> 

Шаблон показывает использование привязок ввода и вывода в TweetComponent. Входные данные [tweet] и [retweet] передают данные в TweetComponent, а выходные данные (action) вызывают метод action для TweetsComponent, когда происходит действие (действие избранного или ретвита).

Чтобы увидеть, как отображаются твиты, давайте перейдем к TweetComponent, который связывает много данных в компонент карты и находится по адресу src/app/tweet/tweet.component.html .

 <div class="card-header"> <img [src]="tweet.user.profile_image_url_https" class="avatar" /> {{tweet.user.name}} (@{{tweet.user.screen_name}}) <span *ngIf="retweet" class="retweeted"><clr-icon shape="sync"></clr-icon> Retweeted by {{retweet.user.name}} (@{{retweet.user.screen_name}})</span> <div class="card-header-actions"> <button type="button" class="btn btn-icon" [ngClass]="{'btn-success': tweet.favorited}" (click)="toggleAction('favorite')"><clr-icon shape="heart"></clr-icon> {{tweet.favorite_count}}</button> <button type="button" class="btn btn-icon" [ngClass]="{'btn-success': tweet.retweeted}" (click)="toggleAction('retweet')"><clr-icon shape="share"></clr-icon> {{tweet.retweet_count}}</button> </div> </div> <div class="card-block"> <div class="card-img" *ngIf="hasPhoto(tweet)"> <img [src]="tweet.entities?.media[0].media_url_https" (click)="media = true" /> </div> <p class="card-text" [innerHTML]="tweet | tweet"></p> </div> <div class="card-footer" *ngIf="!retweet"> {{tweet.created_at | amTimeAgo}} <clr-icon shape="minus"></clr-icon> {{tweet.created_at | date:'medium'}} </div> <div class="card-footer" *ngIf="retweet"> {{retweet.created_at | amTimeAgo}} <clr-icon shape="minus"></clr-icon> {{retweet.created_at | date:'medium'}} </div> <clr-modal [(clrModalOpen)]="media" *ngIf="tweet.entities.media" clrModalSize="lg"> <h3 class="modal-title"><img [src]="tweet.user.profile_image_url_https" class="avatar" /> {{tweet.user.name}} (@{{tweet.user.screen_name}}) <span *ngIf="retweet" class="retweeted"><clr-icon shape="sync"></clr-icon> Retweeted by {{retweet.user.name}}</span></h3> <div class="modal-body"> <img [src]="tweet.entities?.media[0].media_url_https" /> </div> <div class="modal-footer" [innerHTML]="tweet | tweet"></div> </clr-modal> 

Я просто укажу на несколько ключевых аспектов этого шаблона. Во-первых, две кнопки в .card-header-actions показывают количество избранных и ретвитов. У них также есть привязка к событию (click)="toggleAction('favorite')" которая вызывает метод click для обработки действий. Этот метод отправляет событие до TweetsComponent, который использует привязку события (action) для захвата.

Кроме того, вы можете увидеть много интерполяционных привязок, которые являются {{tweet.favorite_count}} . Контент отображается много, поэтому это самый простой способ распечатать текст или контент на странице.

Далее, основной текст твита напрямую связан со свойством innerHTML элемента .card-text , как вы видите здесь. Это потому, что мы хотим отображать контент HTML, а не просто текст, потому что он позволяет нам вставлять контент со ссылками.

 <p class="card-text" [innerHTML]="tweet | tweet"></p> 

Это связывание с innerHTML выполнено, потому что у нас есть собственный канал (который мы рассмотрим чуть позже), который анализирует твит и заменяет часть содержимого ссылками. Так, например, если в твите есть URL-адрес, это заменит значение простого текста на фактическую ссылку на якорь. Точно так же, если твит упоминает другого пользователя, он делает то же самое. Мы также включили каналы amTimeAgo , которые представляют собой набор угловых каналов для управления временем .

Наконец, внизу есть элемент clr-modal , который является модальным из Clarity . Если твит содержит изображение, и пользователь нажимает на изображение (см. Выше в .card-img ), он откроет модальное изображение с увеличенной версией.

Чтобы обернуть этот компонент, полезно просмотреть контроллер компонента в src/app/tweet/tweet.component.ts , который определяет несколько важных атрибутов:

 import { Component, EventEmitter, Output, Input } from '@angular/core'; import { Tweet } from '../tweet'; @Component({ selector: 'app-tweet', templateUrl: './tweet.component.html', styleUrls: ['./tweet.component.scss'] }) export class TweetComponent { @Input() tweet: Tweet; @Input() retweet: Tweet; @Output() action = new EventEmitter<{property: string, tweet: Tweet}>(); hasPhoto(tweet: Tweet) { if (tweet.entities.media && tweet.entities.media.length && tweet.entities.media[0].type === 'photo') { return true; } return false; } toggleAction(property: 'favorite'|'retweet') { this.action.emit({property, tweet: this.tweet}); } } 

Компонент объявляет два входа: @Input() tweet и @Input() retweet и один выход — действие @Output() action . Два входа позволяют нам связывать в твите для отображения, и если это ретвит, мы также связываем в этом твите информацию. Вы видели, как эти значения передаются из шаблона TweetsComponent.

Вывод предупреждает родительский компонент, когда что-то происходит, и в этом случае мы хотим предупредить о действии, чтобы добавить в избранное или ретвитнуть твит при нажатии этих кнопок. Эта информация просто передается, как обычное событие JavaScript, и компонент TweetsComponent будет определять, что с ним делать, с помощью метода action .

Прежде чем мы завершим способ отображения наших твитов, давайте кратко рассмотрим эту TweetPipe, которую мы использовали для форматирования и анализа твита.

Использование TweetPipe для форматирования данных

Последняя важная функция для обзора — это TweetPipe, которая находится по адресу src/app/tweet.pipe.ts и показана ниже. Это обрабатывает синтаксический анализ текста твита и метаданных, чтобы обеспечить форматирование:

 import { Pipe, PipeTransform } from '@angular/core'; import { Tweet } from './tweet'; import { DomSanitizer } from '@angular/platform-browser'; @Pipe({ name: 'tweet' }) export class TweetPipe implements PipeTransform { constructor(private sanitizer: DomSanitizer) {} transform(tweet: Tweet, args?: any): any { let text = this.sanitizer.sanitize(tweet.full_text); if (tweet.entities.user_mentions) { tweet.entities.user_mentions.forEach(mention => { text = text.replace(new RegExp(`@${mention.screen_name}`, 'gi'), `<a href="https://twitter.com/${mention.screen_name}" target="_blank">@${mention.screen_name}</a>`); }); } if (tweet.entities.urls) { tweet.entities.urls.forEach(url => { text = text.replace(url.url, `<a href="${url.url}" target="_blank">${url.display_url}</a>`); }); } if (tweet.entities.media) { tweet.entities.media.forEach(url => { text = text.replace(url.url, ''); }); } text = text.replace(/\n/gm, '<br />'); return this.sanitizer.bypassSecurityTrustHtml(text); } } 

Когда вы создаете пользовательский канал, вы должны реализовать метод transform и вернуть значение, которое вы хотите отобразить. В этом случае мы получаем весь объект твита (не только текст, потому что нам нужны метаданные), и обрабатываем его несколькими способами. Twitter возвращает данные в согласованной структуре, поэтому мы просто проверяем каждое свойство, чтобы выяснить, присутствуют ли какие-либо URL, медиа или упоминания. Если они есть, мы заменяем эти значения ссылкой или, в случае медиа, она удаляется, потому что изображения уже отображаются.

Однако Angular обычно не позволяет вам передавать HTML и связывать его в шаблон по соображениям безопасности. Angular позволяет обойти это и напрямую обрабатывать ввод для дезинфекции. Способ, которым мы решили это здесь, — это сначала очистить текст твита, который удалит любой потенциально опасный контент (например, ссылки с javascript: или тегами скрипта). Затем мы изменяем текстовую строку, чтобы заменить упоминания и ссылки тегами ссылок. Наконец, мы используем метод DomSanitizer.bypassSecurityTrustHtml чтобы обойти ограничения безопасности для отображаемого текста. Тем не менее, так как мы санировали текст в начале, контенту можно доверять.

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

Резюме

Это завершает наш быстрый тур по клиенту Angular для Twitter, и мы увидели много ключевых возможностей Angular на дисплее и узнали, как создать базовый сервер NodeJS, который подключается к API Twitter. Предполагается, что это будет базовый пример, но многие дополнительные возможности могут быть добавлены довольно легко, например, создание твитов, просмотр профилей пользователей и другие взаимодействия. Я рекомендую вам заглянуть в документацию по API Twitter, чтобы узнать, какие у вас есть варианты, и посмотреть, что еще вы можете создать!