Пульс твиттера о веб-разработке
Наверное, уже прошёл год с тех пор, как я написал скрипт для трансляции сообщений из Twitter. Изначально я это делал для маркетинговой конференции Digitale, но востребованность трансляции оказалась намного выше. И в качестве очередного примера я добавил трансляцию сообщений о веб-разработке. Но об этом чуть ниже. Потому что начать я хотел бы с грустного.
Twitter API 1.1
Twitter закрыл первую версию своего API. Все запросы к старой версии будут возвращать статус HTTP 410 Gone. Главное изменение заключается в авторизации. Теперь работает только OAuth авторизация. Ну что же, давайте переделаем нашу трансляцию.
Я не хочу ничего переделывать, я хочу смотреть демонстрацию.
Twitter Streaming API
Для начала, выделим код работы с Twitter Streaming API в отдельную функцию.
http = require 'request'
module.exports = (track, oauth, fn) ->
params =
url: 'https://stream.twitter.com/1.1/statuses/filter.json'
oauth: oauth
form:
include_entities: true
track: track
request = http.post params, (error) ->
fn error, false if error
request.on 'data', (buffer) ->
try
tweet = JSON.parse buffer.toString()
catch error
tweet = false
if tweet
data =
id: tweet.id_str
link: "http://twitter.com/#{tweet.user.screen_name}"
avatar: tweet.user.profile_image_url
login: tweet.user.screen_name
name: tweet.user.name or tweet.user.screen_name
text: tweet.text
fn null, data
Как видите, я решил использовать request модуль. С ним будет проще проходить OAuth авторизацию и разбирать ответ сервера. Вызов функции будет выглядеть так.
oauth =
consumer_key: 'consumer key'
consumer_secret: 'consumer secret'
token: 'oauth token'
token_secret: 'token secret'
filter = require './twitter/filter'
filter 'html5,css3', oauth, (error, tweet) ->
console.log tweet unless error
Остаётся только передавать все новые сообщения клиентам. Например, это можно сделать с помощью socket.io.
clients = []
io = require('socket.io').listen(8080)
io.sockets.on 'connection', (socket) ->
clients.push socket
oauth =
consumer_key: 'consumer key'
consumer_secret: 'consumer secret'
token: 'oauth token'
token_secret: 'token secret'
send = (tweet) ->
client.send JSON.stringify tweet for client in clients
filter = require './twitter/filter'
filter 'html5,css3', oauth, (error, tweet) ->
send tweet unless error
OAuth авторизация для Twitter API
Осталось получить OAuth ключи и можно запускать. Я думал, что этот процесс достаточно простой, но вчера увидел сообщение от @miripiruni и решил подробнее остановиться на этой задаче. Так как трансляция будет работать на серверной стороне, то нам будет достаточно девелоперских ключей.
Итак, у нас есть все четыре ключа: «consumer key», «consumer secret», «access token» и «access token secret».
Вывод через socket.io
Теперь дело за клиентской частью. Простой вариант вывода сообщений трансляции на страницу.
(function(window, document) {
'use strict';
var socket = window.io.connect('http://127.0.0.1:8080'),
// Получаем шаблон
template = document.getElementById('twitter-template').innerHTML,
// Контейнер со всеми сообщениями
container = document.getElementById('twitter-feed'),
messages = [];
// Все новые сообщения добавляем в массив messages
socket.on('connect', function() {
socket.on('message', function(result) {
messages.push(JSON.parse(result));
});
});
// Каждые 2 секунды берём первое сообщение из массива и показываем
window.setInterval(function() {
if (messages.length > 0) {
var message = messages.shift(),
// Получаем все сообщения в ленте
tweets = container.querySelectorAll('section'),
length = tweets.length,
section = document.createElement('section');
// Создаём HTML шаблон для нового сообщения
section.innerHTML = mustache(template, message);
if (length > 0) {
container.insertBefore(section, tweets[0]);
} else {
container.appendChild(section);
}
// Если в ленте больше 20 сообщений, удаляем самое старое
if (length > 20) {
container.removeChild(tweets[length-1]);
}
}
}, 2000);
})(window, document);
В таком виде мы получаем работающую трансляцию всех сообщений по заданным хештегам.
Пульс твиттера о веб-разработке
Для демонстрации я решил подготовить немного улучшенный вариант. Он транслирует большинство сообщений о веб-разработке на русском и английском языке. Как правильно заметил @nesterov_vit — очень похоже на телевизор.
Теперь предлагаю остановиться подробнее на тех улучшениях, которые я добавил.
Серверная часть
Во-первых, хотелось убрать нежелательные сообщения из трансляции. Для этого я подготовил несколько правил:
- сообщения только на русском и английском языке;
- сообщения от пользователей с нестандартными аватарами;
- игнорировать ретвиты и ответы;
- игнорировать заданный список аккаунтов;
- игнорировать заданные слова в сообщении;
- игнорировать сообщения с переизбытком хештегов.
Все эти правила я выделил в отдельную функцию.
twitter = require 'twitter-text'
options =
# список нежелательных аккаунтов (для тролей)
mute: ['']
# список стоп-слов (для спама)
spam: ['купить javascript без смс']
module.exports = (tweet) ->
return false if not tweet or not tweet.user or not tweet.text
# Разрешены сообщения только на русском и английском
if tweet.lang?
return false if tweet.lang not in ['en', 'ru']
else if tweet.user.lang?
return false if tweet.user.lang not in ['en', 'ru']
# Пропускаем нежелательные аккаунты
if options.mute.length > 0
account = "#{tweet.user.screen_name}".toLowerCase()
return false for mute in options.mute when account is mute
# Пропускаем пользователей с аватаром по-умолчанию ^_^
if tweet.user.profile_image_url.indexOf('default_profile_images') > -1
return false
text = "#{tweet.text}"
# Пропускаем старые ретвиты и ответы на сообщения
if text.indexOf('RT ') is 0 or text.indexOf('@') is 0 or text.indexOf('.@') is 0
return false
if options.spam.length > 0
# Преобразуем сжатые ссылки в полный вид
if tweet.entities.urls? and tweet.entities.urls.length > 0
for entity in tweet.entities.urls
text = text.replace entity.url, entity.expanded_url
text = text.toLowerCase()
# Пропускаем твиты содержащие в себе стоп-слова
return false for spam in options.spam when text.indexOf(spam) isnt -1
# Пропускаем твиты содержащие переизбыток хештегов ^_^
hashtags = twitter.extractHashtagsWithIndices text
return false if hashtags and hashtags.length > 4
return true
Во-вторых, можно поработать над текстом сообщений. Вот, что мы с ними сделаем:
- все сжатые ссылки преобразуем в нормальные;
- все ссылки на картинки преобразуем в изображения;
- заменим размер аватаров на максимально возможный.
Опять же, этим будет заниматься отдельная функция.
twitter = require 'twitter-text'
module.exports = (tweet) ->
# Преобразуем только ссылки, хештеги и аккаунты оставляем текстом
text = twitter.autoLinkUrlsCustom tweet.text, target: '_blank'
# Заменяем сжатые ссылки на нормальные
if tweet.entities.urls? and tweet.entities.urls.length > 0
for entity in tweet.entities.urls
text = text.replace entity.url, entity.expanded_url
text = text.replace entity.url, entity.display_url
# Заменяем ссылки на картинки на сами изображения
tweet.media = false
if tweet.entities.media? and tweet.entities.media.length > 0
for entity in tweet.entities.media
text = text.replace entity.url, entity.expanded_url
text = text.replace entity.url, entity.display_url
tweet.media =
url: entity.media_url
w: entity.sizes.small.w
h: entity.sizes.small.h
tweet.text = text
# Заменяем размеры аватаров на максимальные
image = tweet.user.profile_image_url
pos = image.lastIndexOf '_'
tweet.user.profile_image_url = image.substring(0, pos) + '_bigger' + image.substring(pos + 7)
tweet
Клиентская часть
В этой части я постарался сконцентрироваться на удобстве.
Во-первых, я добавил статус подключения к серверу. Страница может быть открыта продолжительное время, и не всем будет понятно, работает трансляция или нет.
Во-вторых, из-за того, что Twitter Streaming API возвращает только текущие сообщения, то может произойти так, что новый пользователь долгое время ничего не увидит. Чтобы этого избежать, сразу показываю 5-10 сообщений из поиска. Для этого я использую Twitter REST API. При этом новые сообщения из Streaming API будут поступать в очередь сразу за старыми.
В-третьих, время сообщения отображается, как время прошедшее с публикации. И, чтобы оно оставалось в актуальном состоянии, раз в минуту оно пересчитывается.
В-четвёртых, вся видимая лента хранится в sessionStorage
. Это сделано, чтобы избежать случайных обновлений страницы и других похожих ситуаций. Таким образом информация всегда будет на своём месте.
Ну что, посмотрим демонстрацию?
Анализ сообщений
Было бы здорово к этому прикрутить анализ сообщений. И пытаться выявить наиболее интересные темы за день. Но это, видимо, не мой уровень. Может кто-то из Яндекса придёт и скажет, как это сделать, а?