Skype Preview — плагин предпросмотра ссылок в скайпе

skype

Сидел я как-то в одном уютном чатике в Skype, и тут одному из участников в голову пришла весьма интересная идея: сделать плагин для скайпа, который позволял бы просматривать содержимое тех ссылок, которые вкидываются в чат, без их открытия. "А почему бы и нет?", - подумал я. Можно открывать картинки в уменьшенном варианте, если ссылка указывает на изображение (в том числе анимированные). А если ссылка ведет на обычную html-страничку, то можно отображать ее заголовок. Забегая вперед, покажу, что получилось:

plugin

skype1

skype2

А теперь немного истории и деталей кодинга. Желающих сразу скачать плагин прошу проследовать в конец статьи. Итак, я тут же открыл Visual Studio полез в гугл, чтобы посмотреть, какие на сегодняшний день есть открытые API у Skype. Как, к сожалению, оказалось, SDK никакого у скайпа нет уже давно. Раньше имелся некий SDK на COM-интерфейсах (skype4com), но от его поддержки отказались, и использовать его было бы уже неразумным. Остался ущербный мини-API на URI (вот его описание), но на этом сделать что-либо толковое не представлялось возможным. Таким образом, единственным путем запиливания своего функционала в Skype осталось исследование внутренностей программы и попытки внедрения в нее своего кода (хуки, перехваты оконных сообщений или что-то еще). К счастью, пользовательский интерфейс скайпа построен на окнах (обычных окнах Windows, без всяких WPF и подобного), и это играет нам на руку, потому что можно будет перехватывать оконные сообщений и WinAPI-функции, ответственные за отрисовку текста (напомню, что требуется отображать предпросмотр содержимого ссылки при наведении на нее курсора). В результате я пришел к таким решениям по изменению функционала скайпа:

1. Как добавить в меню скайпа собственный пункт меню? Я перехватил WinAPI-функции DrawMenuBar и PeekMessageW. В PeekMessageW мы проверяем, является ли поток, вызвавший функцию, потоком, в котором работает главное окно скайпа, и если это так, то производится перерисовка меню с помощью уже хукнутой функции DrawMenuBar, после чего отключается хук PeekMessageW. Это нужно для случая, когда плагин инжектится в уже работающий скайп. Если плагин инжектится в скайп до запуска, то скайп, скорее всего, сам вызовет нашу похуканную функцию DrawMenuBar, но подстраховаться не помешает в любом случае. Что же происходит внутри перехваченной DrawMenuBar? В целом, ничего сложного: производится сабклассинг главного окна скайпа (чтобы повесить обработку кликов по добавленному меню) и непосредственно добавляется меню плагина.

2. Как понять, что пользователь навел курсор на активную ссылку в окне чата скайпа? Для этого я перехватил WinAPI-функцию ExtTextOutW. Ей пользуется Skype при отрисовке текста. Непросто было отследить, когда именно отрисовывается активная ссылка в окне чата, и по-прежнему такой режим плагина работает не слишком стабильно (иногда плагин считает, что пользователь навел курсор на ссылку, хотя это не так), но допиливать до идеала мне пока что лениво. Итак, в перехваченной ExtTextOutW выполняется проверка, что вызов пришел именно из модуля Skype.exe, что курсор виден и ни одна кнопка мыши не нажата, и что передан именно URL, начинающийся с http:// или https://. Иногда Skype любит перерисовывать ссылки в открытом окне чата вне зависимости от того, водит ли пользователь курсором над ним, и именно в этом случае плагину может показаться, что пользователь провёл курсором над ссылкой. Все хуки выполнены в файле main.cpp проекта, и в качестве упражнения могу предложить людям, понимающим в кодинге, понять, как можно поточнее отделить именно действие пользователя в таком сценарии от какой-то рандомной перерисовки ссылки. Для начала, например, можно попробовать убедиться, что курсор находится действительно над окном сообщений Skype.

3. В плагине есть режим работы, когда превью ссылки отображается при клике на ссылку, а саму ссылку можно открыть двойным кликом. Это сделано простым хуком функции ShellExecuteW. Самым сложным было написать свой обработчик, отличающий клик от двойного клика, и он сделан на boost::asio (double_click_monitor.h, double_click_monitor.cpp).

Теперь еще кратко о сборке. Я решил не пилить свои велосипеды и по максимуму воспользовался готовыми библиотеками. Вот список библиотек, которые требуются для того, чтобы собрать полноценную версию плагина:
1. curl 7.35.0 - ну, тут понятно. Используется для серфинга по Web'у с поддержкой cookies, редиректов.
1.1. OpenSSL 1.0.0e - используется при сборке curl для того, чтобы работали https-ссылки.
1.2. zlib 1.2.8 - используется при сборке curl для того, чтобы работал gzip.
1.3. libidn 1.28 - используется при сборке curl для того, чтобы поддерживались кириллические и прочие японские домены. Без этой библиотеки можно обойтись, собрав curl так, чтобы он использовал WinAPI, предоставляющее аналогичную функциональность, но работать это будет только на Windows Vista и выше. Чтобы собрать curl с libidn под Windows, надо будет знатно потрахаться с CMakeLists curl'а (потому что батники для сборки под Windows, поставляемые с curl'ом, не умеют подцеплять libidn). Если кому-то будет это интересно, могу рассказать детали в комментариях.
2. libiconv - для того, чтобы можно было отображать превью заголовков страниц в разных кодировках. Сборка под Windows осуществлялась с помощью этого проекта.
3. MinHook - для осуществления хуков WinAPI-функций.
4. Boost 1.55 - для прочих нужд вроде отслеживания двойных кликов в окне скайпа или сохранения-загрузки настроек в XML-формате.
5. GdiPlus - стандартная библиотека Windows, используется для изменения размеров изображений и рисования текста.

Помимо непосредственно dll-файла, который инжектится в адресное пространство скайпа, я написал еще и загрузчик (exe-файл), который позволяет подгрузить плагин в скайп (если тот уже запущен), либо запустить скайп с загруженным плагином.

Скачать комплект для сборки можно ЗДЕСЬ (только проекты для VS2010 без библиотек и бинарников). Все проекты собираются в Visual Studio 2010. Для сборки плагина и лоадера используется конфигурация Debug или Release. Конфигурация DebugExe позволяет создать вместо dll-файла отладочную версию плагина (exe-файл, см код в main.cpp). Если надумаете собирать всё сами, то приготовьтесь к знатному баттхерту выкачиванию большого количества библиотек и их не очень простой сборке. В настройках проектов Visual Studio все пути к библиотекам указаны так, как они лежали у меня на компьютере, поэтому вам при сборке придется все пути подправить под себя.

Для тех, кто не читал статью (или кто читал, и не хочет собирать): СКАЧАТЬ ПЛАГИН (БИНАРНИКИ). Запускать SkypeLoader.exe (неважно, запущен ли сам Skype). Можно поместить SkypePreview.dll и SkypeLoader.exe в любую папку, после чего создать на рабочем столе или в меню "Пуск" ярлык для SkypeLoader.exe, а потом пользоваться им для запуска Skype.

Skype Preview — плагин предпросмотра ссылок в скайпе: 27 комментариев

  1. Windows 7 x86
    Скайп часто крашится. Подсказка отображается (а то и не отображается) через многочисленные глюки (гифка загрузки глючит (зацикливается на первых кадрах), подсказка не отображается/отображается после двигания мышкой.

    Короче к употреблению не пригодно.

    1. А может у кого-то засраная непойми чем система? На нескольких системах: XP x86 SP3, 7 x64 SP1, 7 x86 SP1 - в течение 2 недель никаких глюков не наблюдается и уж тем более падений.

  2. Хорошая попытка. Но мимо. Я не любитель программ "Запустить Интернет" и тому подобных. Если имеется ввиду, что ещё что-то инжектится в процесс (банерорезка етц) - то тоже мимо.
    Следующим советом будет переустановить винду? :)
    Кроме падений - глюков и так хватает. Наводим мышь на первую ссылку, переводим на вторую. Получаем на второй ссылке заголовок первой. Не всегда адекватно обрабатывает редиректы, например yandex.ru > yandex.[страна] > http://www.yandex.[страна] Периодично устраивает мини DoS, пока не отведёшь мышку.
    Неадекватно обрабатывает несколько ссылок в одном сообщении (ссылки через энтер).

    Виновата засранность - WIN!

    1. Следующим советом будет переключить режим открытия превью на одинарный клик. А после этого будет предложение взять исходный код и переписать как угодно. И, наконец, я это, как его, нетерпимо отношусь к критике. Улавливаешь?

  3. И что-то я не понял манипуляций с DrawMenuBar и PeekMessageW. Зачем это всё? Если мы уж перехватываем апи, то почему бы не перехватить createwindowex (класс то уникальный), получить hwnd, нацепить свой обработчик на главное окно и, например, в WM_ACTIVATE добавить пункты меню (ну и поставить маркер - пропускать последующие WM_ACTIVATE).

    1. Можно и так, почему нет. Я не мастер хуков, поэтому мое решение может оказаться не лучшим.
      Хотя с CreateWindowEx я, кажется, смотрел. Припоминаю, что скайп своё окно создает 1 раз и потом просто модифицирует в нём меню, удаляя-добавляя пункты, и полностью его заменяет.

  4. По идее при WM_ACTIVATE меню уже должно быть готовым. Но это тоже не ахти способ. И не факт, что в процессе работы скайп не начнёт заново ставить меню, что-то удаляя, добавляя.
    А на win7 мой пример с перехватом CreateWindowEx вообще крашится, если вместо имени класса приходит атом. Мои познания пока не позволяют с этим справиться. :(

  5. В Skype-чатах есть дурацкая возможность удалять свои сообщения, возможно ли подобным образом написать плагин, который не будет удалять сообщения в моем окне чата?

  6. Можно было бы поступить проще, есть такая штука как Accesibility. Открываете прогу Inspect (у меня она называется Inspect Objects, идет в поставке Visual Studio), наводите на контрол TChatContentControl (у меня есть подозрение что на самом деле это на самом деле компонент IWebBrowser, но не суть), и вся структура окна чата как на ладони. Ссылки даже выделены отдельным идентификатором.
    Вот какой-то примерчик https://www.autoitscript.com/forum/topic/130813-possible-to-manipulate-a-skype-tchatcontentcontrol/

  7. Просто пару комментариев по коду, по мелочи:
    curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0);

    Ну да, действительно, зачем нам проверять сертификаты хоста? Соберите лучше libcurl с WinSSL чтобы не возиться с сертификатами openssl.

    curl_easy_setopt(curl_, CURLOPT_PROXYUSERPWD, (proxy_login + ":" + proxy_password).c_str());

    По хорошему тут надо делать urlencode для логина и пароля.

    Ваша функция replace_html_entities плохо работает с юникодом.
    С этим она не справится:

    replace_html_entities("Foo © bar 𝌆 baz ☃ qux");

    Набросал исправление (можно было заюзать iconv, но я не стал):

    https://gist.github.com/zenden2k/e37012806558eb96a9ee#file-utf16_to_utf32-cpp-L132

  8. Заинтересовался вашей опцией "Системный прокси-сервер", и понял, что она работает неправильно. Например, если запустить Fiddler, функция get_proxy_info возвращает "http=127.0.0.1:8888;https=127.0.0.1:8888;ftp=127.0.0.1:8888", но curl такое скушать не может и выдает ошибку. Там все не так просто. Вот наткнулся на код http://curl.haxx.se/mail/lib-2013-01/att-0327/proxy.cpp но как-то мудрено там.

    1. Спасибо, как появится время, поисправляю. Это в любом случае не единственные баги в коде, его же никто не тестировал почти :)

  9. Хех, плагин наверно уже будет не актуален,
    в новой версии скайпа для desktop и скайп for web ссылки отображаются по новому.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *