Кросспостинг из Twitter в ВКонтакте с помощью роутера

в 9:17, , рубрики: EyeEm, python, twitter, twitter api, zyxel keenetic, Вконтакте, Вконтакте API, метки: , , , , , ,

Здравствуйте, меня зовут Евгений, и я алкоголик люблю социальные сети. В силу отсутствия каких-либо намеков на писательский талант я предпочитаю Twitter — его ограничение на 140 символов избавляет от необходимости придумывать что-то длинное. К тому же, только для твиттера есть нормальный java-клиент, которым я раньше пользовался на мобильном телефоне.
С другой стороны, бóльшая часть моя знакомых использует ВКонтакте, да и мне, честно говоря, нравится слушать там музыку и читать новости. Очевидно, что писать самому в две разных соцсети неудобно, нужно это автоматизировать — использовать кросспостинг (перенос постов).
Далее небольшое описание моего не совсем обычного способа кросспостинга.

Еще немного предыстории

Ранее для переноса записей я использовал IFTTT. Это было не слишком удобно — посты переносились раз в 15 минут, спецсимволы энкодились (" превращалось в "), ссылки переносились сокращенными (t.co) — Вконтакте ругался при попытке перейти по этим ссылкам. Однако из-за недавних изменений в Twitter API, IFTTT был вынужден отключить «рецепты», которые читают твиты. Мне пришлось искать новый путь. Внезапно™ я узнал, что у Вконтакта есть официальный способ импортировать твиты, но для этого приходится использовать хэштег #vk. После пары дней тестирования, оказалось что работает это не всегда (видимо опять срабатывали новые ограничения Twitter API на количество запросов), плюс оставалась проблема с ссылками.
Конечно, самым простым способом было бы использование скрипта, который будет переносить записи. На хабре неоднократно писали о разных способах кросспостинга, но все эти способы упирались в отсутствие у меня своего сервера. Покупать же платный хостинг (пусть и недорогой) для одного простенького скрипта смысла мало.
Но вот недавно, ползая по интернету в поисках новой версии прошивки для своего роутера, меня осенило, что на базе моего Zyxel Keenetic можно сделать небольшой веб-сервер! Еще перед покупкой я читал о таких возможностях роутера, но потом это как-то вылетело из моей головы. И вот я приступил к реализации задуманного проекта — кросспостинга из Twitter в Вконтакте с помощью роутера.

Москва не сразу строилась

Для начала немного расскажу о возможностях Zyxel Keenetic. Вкратце, на роутер можно быстро, легко и безопасно устанавливать различные готовые модули (php, lighttpd, dlna, transmission, perl, python и др). Полее подробно можно почитать на форуме.
Перед началом проекта я сформировал список хотелок. Скрипт должен был уметь:

  • переносить твиты сразу после их появления либо при запуске скрипта через cron
  • не переносить ответы, ретвиты и упоминания
  • разворачивать сокращенные ссылки t.co в тексте твита
  • по возможности копировать приложенные картинки (например, Instagram)

Cначала надо было выбрать язык скрипта. Для роутера есть модули Perl и Python, и с этими языками я был одинаково незнаком. С Twitter API и Вконтакте API я так же никогда не сталкивался, поэтому в первую очередь меня интересовали готовые примеры работы с ними. К счастью, таких скриптов в сети было найдено достаточно. Как-то случайно я выбрал Perl:)
Затем я приступил непосредственно к документации и примерам по Twitter API. Как вы знаете, есть два типа — Streaming и REST. В последних изменениях в API явно проглядывается желание Twitter'а перевести разработчиков на Streaming API. Поэтому было решено использовать именно Streaming, чтобы не иметь проблем в будущем.

Так как я делал скрипт только для себя, то я не стал делать получение access token, а взял его сразу со страницы настроек моего Twitter-приложения. За пару часов с помощью perl-модуля было написано простенькое консольное приложение, выводившие твиты. На моем компьютере все работало отлично. Для проверки на роутере установил все нужные пакеты (как мне казалось), запустил скрипт, и… ничего. Не работает. Я установил все имеющиеся пакеты для Perl, не помогло. Путем гугления по тексту ошибки выяснил, что скорее всего, проблема из-за отсутствия какого-то perl-модуля, в результате скрипт не может правильно работать с SSL. В итоге было решено оставить Perl, и попробовать Python.

Perl умер, да здравствует Python

Скачал PyCharm, стал изучать синтаксис. Если честно, после .NET был в шоке от использования отступов в качестве обозначения блоков:) Разобрался в синтаксисе, нашел модуль tweepy, и довольно быстро мой скрипт вывода твитов был переписан на Python. Устанавливаю пакеты python на роутер, запускаю скрипт — о, чудо! Работает! Осталось дописать побочный функционал и запись постов в Вконтакте.
Для начала разберемся с функционалом.
Разворачивание ссылки. Если твит содержит ссылку, то Twitter возвращает нам JSON, в котором есть элемент "entities", содержащий "urls". Выглядит это примерно так:

"entities":
{
	"hashtags":[],
	"user_mentions":[],
	"urls":[{
		"indices":[0,21],
		"display_url":"dev.twitter.com/terms/display-u2026",
		"url":"https://t.co/Ed4omjYs",
		"expanded_url":"https://dev.twitter.com/terms/display-guidelines"
	}]
}

В тексте твита содержится "url", поэтому для разворачивания ссылки нужно просто в тексте заменить значение "url" на значение "expanded_url".
Получение изображения. Если к твиту приложена картинка, то в "entities" добавится элемент "media", вот так:

"entities":
{
	"hashtags":[],
	"user_mentions":[],
	"urls":[],
	"media":[{
		"type":"photo",
		"media_url":"http://p.twimg.com/A7kqLpACEAAUlwz.png",
		"indices":[0,20],
		"sizes":
		{
			"large":{"resize":"fit","h":454,"w":584},
			"small":{"resize":"fit","h":264,"w":340},
			"thumb":{"resize":"crop","h":150,"w":150},
			"medium":{"resize":"fit","h":454,"w":584}
		},
		"display_url":"pic.twitter.com/XzDoEpH9",
		"media_url_https":"https://p.twimg.com/A7kqLpACEAAUlwz.png",
		"url":"http://t.co/XzDoEpH9",
		"expanded_url":"http://twitter.com/TestTwVK/status/268292032273977344/photo/1",
		"id":268292032278171648,
		"id_str":"268292032278171648"
	}]
},

Прямая ссылка на изображение содержится в элементе "media_url". Мне пришлось сохранять картинку во временный файл на локальном диске, не нашел способа загрузить его в ВК напрямую с сервера твиттера.

Посмотрим на получивший код для работы с Twitter.
Сначала авторизуемся и начинаем читать стрим

url = "https://userstream.twitter.com/1.1/user.json"
param = {"delimited":"length", "with":"user"}
header = {}
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_key, access_secret)
auth.apply_auth(url, "POST", header,param)
logging.info('Twitter authorization successful')
#запрос твитов
req = urllib2.Request(url)
req.add_header("Authorization", header["Authorization"])
r = urllib2.urlopen(req, urllib.urlencode(param), 90)

Читаем ответы, если это твит — обрабатываем его

while True:
    #получаем длину твита
    length = ""
    while True:
        c = r.read(1)
        if c == "n": break
        if c == "": raise Exception
        length += c
    length = length.strip()
    if not length.isdigit(): continue
    #читаем ответ указанной длины
    tweet = json.loads(r.read(int(length)))
    #если это твит - запускаем его обработку
    if "user" in tweet and "text" in tweet and "created_at" in tweet and "id" in tweet:
        handleTweet(tweet)

Метод обработки твита:

#получаем текст твита
text = tweet["text"]
#проверяем, что это не ответ, ретвит или упоминание
if not text.startswith('@') and not "@zzeneg" in text:
    #получаем ссылки в твите
    urls = tweet["entities"]["urls"]
    #заменяем сокращенные ссылки
    for url in urls:
        text = text.replace(url["url"], url["expanded_url"])
        #если ссылка ведет на EyeEm - получаем id фото
     #если приложена картинка - убираем ее из текста
     if "media" in tweet["entities"]:
        photo = tweet["entities"]["media"][0]
        text = text.replace(photo["url"], "")
Работа с ВКонтакте

У ВК немного другой механизм авторизации, и просто так получить access token не получится.
Сначала нужно создать desktop-приложение и взять его id. Затем сформировать ссылку вида https://oauth.vk.com/authorize?client_id={ID}&scope=wall,photos,offline&redirect_uri=http://oauth.vk.com/blank.html&display=page&response_type=token, где {ID} — это ID приложения. В данной ссылке так же указаны разрешения для приложения — wall для постинга на стену, photos для заливки изображения и offline для того чтобы полученный токен был вечным. Копируем данную ссылку в браузер. ВК спросит нас о предоставлении прав данному приложению, а затем перенаправит на URL, в котором будет содержаться токен.

Для работы с методами VK API используется вот такая обертка. Код не мой, но я совершенно забыл откуда его взял — прошу прощения, что не указываю автора.

def vkMethod(method, data={}):
    url = 'https://api.vk.com/method/%s.json' % (method)
    data.update({'access_token': vkToken})
    response = requests.post(url, data).json
    if 'error' in response:
        print 'VK API error: %s' % (response['error']['error_msg'])
    return response

Здесь vkToken — полученный токен для ВК, method — имя метода из списка методов VK API, data — какие-то данные для метода.

Чтобы прикрепить картинку к посту на стене, нужно загрузить ее в альбом пользователя. Для фотографий на стене в ВК есть специальный альбом и специальные методы для работы с ним. Сначала получаем ссылку для загрузки фото, затем сохраняем картинку. В итоге получился такой метод:

def uploadPhoto(fileUrl):
    #получаем url для загрузки фото
    response = vkMethod('photos.getWallUploadServer')
    uploadUrl = response['response']['upload_url']
    #сохраняем фото локально
    urllib.urlretrieve(fileUrl,'temp.jpg')
    #загружаем фото в ВК
    files = {'photo': open('temp.jpg', 'rb')}
    response = requests.post(uploadUrl, files = files).json
    #добавляем фото в альбом
    response = vkMethod('photos.saveWallPhoto', response)
    #возвращаем id фото
    return response['response'][0]['id']

Данный метод возвращает ID загруженной картинки, который можно указать непосредственно при создании сообщения на стене:

attachments = uploadPhoto(photo["media_url"])
vkMethod('wall.post', {'message': text,'attachments':attachments})

При этом, если attachments будет пустой строкой — то сообщение все равно появится на стене, без вложений.

Работа с EyeEm

Я использую EyeEm (это аналог Instagram, но с нормальными Privacy Terms), и недавно подумал, что фото оттуда хорошо бы загружать в ВКонтакте. К счастью, у EyeEm есть очень простой и понятный API, и буквально в две строчки в скрипт можно добавить поддержку загрузки фоток из него.
Метод получения ссылки на фото по ее ID

def getEyeEmPhotoUrlById(photoId):
    url = "https://www.eyeem.com/api/v2/photos/{0}?access_token={1}".format(photoId, eyeEmToken)
    response = requests.get(url).json
    return response['photo']['photoUrl']

Теперь находим ссылку на фото в твите, получаем ее url и так же загружаем в ВК

if url["expanded_url"].startswith("http://www.eyeem.com/p/"):
    eyeEmId = url["expanded_url"].replace("http://www.eyeem.com/p/", "")
    photoUrl = getEyeEmPhotoUrlById(eyeEmId)
    attachments = uploadPhoto(photoUrl)
Установка скрипта на роутер

Готовый скрипт можно скачать с github. Токены в нем, естественно, изменены:) Нужно будет получить свои.
Теперь настравиваем роутер для поддержки сторонних пакетов по инструкциям на вики. Приведу здесь краткую инструкция для Windows:

  1. Форматируем флэшку в NTFS, создаем на ней папки system/bin
  2. Вставляем флэшку в роутер, открываем ее в проводнике и копируем туда файл ext_init.sh
  3. Перемонтируем диск — через веб-интерфейс отключаем диск, перевтыкаем флэшку. Ждем пока в логах роутера появится запись "dropbear[4017] Running in background"
  4. Подключаемся по SSH (root/zyxel) и выполняем finish_install.sh. Ждем окончания создания swap.

Роутер готов к установке opkg пакетов. Их список можно посмотреть на сайте либо командой opkg list. Устанавливаем python и необходимые модули:
Затем, устанавливаем python с помощью команды:

  1. Основные пакеты с помощью команд opkg install python и opkg install python-openssl
  2. Модули tweepy и requests простым копированием папки в systemusrlibpython2.7. Новая версия requests у меня не заработала, попробуйте либо ветку cache, либо из архива.
  3. Для активации cron переименовываем файл K02cron в папке systemetcinit.d в S02cron
  4. Настраиваем запуск скрипта в cron. В файл systemetccrontabsroot добавляем строку
    */15 * * * * killall -9 "python" ; /media/DISK_A1/system/usr/bin/python  /media/DISK_A1/system/root/TwVk.py

    Теперь cron в 0/15/30/45 минут каждого часа будет убивать скрипт и запускать его заново.Это очень криво, но стандартная для cron команда для действия после перезагрузки @reboot не заработала. Ну и плюс скрипт тоже не совсем идеален, перезапуск ему не повредит. Если кто-то сможет предложить идею получше — милости прошу.

  5. Ну и наконец копируем скрипт в systemroot и опять перемонтируем диск.
Заключение

Данный скрипт работает у меня уже около полугода (статья писалась ооочень долго), большинство ошибок я исправил. Мою стену в ВК стало гораздо удобнее читать из-за развернутых ссылок и прикрепленных фоток. Я доволен. Планирую создать еще пару скриптов для Twitter на основе роутера, например твиттер-бота.

Надеюсь, данная статья кому-нибудь пригодится. Хотя бы чтобы начать использовать имеющийся роутер не только по его прямому предназначению:)

Я честно постарался исправить все грамматические ошибки и опечатки, но если Вы что-то заметите — пишите в личку.
Так же я еще раз напоминаю, что мои знания в Linux и Python недалеко ушли от нуля, и я с удовольствием выслушаю Ваши подсказки по улучшению скрипта.

Благодарности

  • zyxmon, человек и пароход форум. Его вклад в сообщество Zyxel невозможно переоценить.
  • kethlin_mil, своей девушке, без нее я бы не разобрался с linux, cron и Perl.

Автор: zzeneg

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js