Написание протокольного плагина для Miranda

6 апреля 2010 г.

Введение

Я сторонник использования универсальных программ-комбайнов. Если почтовик, то и забирает RSS, если IM, то должен содержать возможности общения по всем популярным протоколам. В этои виде мне больше всего подошла Miranda. Огромное число плагинов позволяет конструировать тот клиент, который тебе нужен. Однако, как оказалось на данный момент не существует плагина под популярную социальную сеть — Twitter, введение OAuth-авторизации убило единственный существующий плагин. Поэтому я принял решение написать свой плагин.


Ожидал, судя по количеству плагинов, что документация проста понятна и удобна в использовании. Но здесь закралась проблема — документации попросту нет. Зато есть открытые исходники и множество opensource-плагинов. Решил немного заполнить этот пробел данной статьей.

Написание плагина

Как и в большинстве плагинных систем, в Миранде плагин представляет собою PE-файл в виде динамически-подгружаемой библиотеки (DLL) со стандартизированным экспортом:

__declspec(dllexport) PLUGININFO *MirandaPluginInfo(ULONG mirandaVersion);
__declspec(dllexport) int Load(PLUGINLINK *link);
__declspec(dllexport) int Unload();

Собственно, функционал данных экспортируемых функций ясен из их названий. Внутри MirandaPluginInfo заполняем информацию о нашем плагине — название, автор, копирайты, версия, и так далее. Load/Unload отвечают за загрузку/выгрузку плагина из системы.

Теперь необходимо упомянуть о том как плагин взаимодействует с Мирандой. Здесь тоже всё просто — структура PLUGINLINK, передаваемая при загрузке, содержит набор коллбеков — функций, принадлежащих IM, которые мы должны вызывать если нам нужно связаться с Мирандой. Например, добавить контакт в контакт-лист или обновить статус. Каждый раз использовать структуру для вызова коллбека не очень красиво и удобно, поэтому в заголовочном файле определён ряд соответствующих макросов. Вместо pluginLink->CreateHookableEventа вызываем CreateHookableEventа. Существует небольшая тонкость — эти макросы-хелперы полагаются на то, что у нас существует глобальная переменная pluginLink, и называется именно так. Если нужно использовать API мессенджера в других модулях своего плагина, то не используейте static, как рекомендуется для глобальных переменных. Исходя из структуры этих макросов, онда из первых строк кода в Load() будет pluginLink = link;.

Я вкратце описал связь в одну сторону. Нужно сказать и про обратные коммуникации, те в которых Миранда обращается к нашему плагину. Они делятся на 2 типа — хуки (перехваты, захваты в различных терминологиях) и сервисные функции. Главное различие в том, что хуки обрабатываются по цепочке (то есть введение нашего хука не отменяет выполнения предыдущих зарегестрированных перехватов), а сервисные функции — только один раз. Пример функции загрузки:

// Миранда загружает наш плагин
__declspec(dllexport) int Load(PLUGINLINK *link)
{
PROTOCOLDEscriptOR protoDesc;

pluginLink = link;

logWrite("Load() called.\n");
// регистрируем плагин
protoDesc.cbSize = sizeof(protoDesc);
protoDesc.szName = PLUGIN_PROTO_NAME;
// да, это протокол
protoDesc.type = PROTOTYPE_PROTOCOL;
CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&protoDesc);

// вешаем нужные нам сервисные функции
// Функция возвращает характеристики плагина, его возможности
hGetCaps = CreateServiceFunction(PLUGIN_PROTO_NAME PS_GETCAPS, servGetCaps);
// Функция возвращает имя протокола
hGetName = CreateServiceFunction(PLUGIN_PROTO_NAME PS_GETNAME, servGetName);
// Функция возвращает загруженную иконку для протокола (вызывается несколько раз, с разными параметрами)
hLoadIcon= CreateServiceFunction(PLUGIN_PROTO_NAME PS_LOADICON,servLoadIcon);
// Меняем статус
hSetStatus = CreateServiceFunction(PLUGIN_PROTO_NAME PS_SETSTATUS, servSetStatus);
// Получаем статус протокола
hGetStatus = CreateServiceFunction(PLUGIN_PROTO_NAME PS_GETSTATUS, servGetStatus);
// Получили мессагу
hRecvMessage = CreateServiceFunction(PLUGIN_PROTO_NAME PSR_MESSAGE,servRecvMessage);

allContactsOnline();

// вышаем хук на загрузку всех модулей
// он нужен нам для того, чтобы повесить хуки на другие модули миранды (не Systcode), которые могут быть еще не загружены
hHookModulesLoad = HookEvent(ME_SYSTcode_MODULESLOADED, hookModulesLoaded);
return 0;
}

В целом должна быть понятна структура плагина, однако, я думаю, стоит привести еще несколько небольших полезных фрагментов кода.

Маленькие полезности

Когда наш протокол получает сообщение, Миранда об этом чаще всего ничего не знает, потому что мы не используем её каналы связи. Поэтому нужно уведомить её о том, что мы получили сообщение:

// отправляем полученную мессагу по цепочке
protoRecvEvent.timestamp = (ULONG)now;
protoRecvEvent.szMessage = text;
callContactData.szProtoService = PSR_MESSAGE;
callContactData.lParam = (LPARAM)&protoRecvEvent;
callContactData.hContact = getContact(authorName, authorId);
if (!callContactData.hContact)
return ;
CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&callContactData);

Создание контакта и привязка его к нашему протоколу:

// нет, такого нет, создаём новый
hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
if (!hContact)
return NULL;
// коннектим протокол к нему
if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)PLUGIN_PROTO_NAME) != 0)
{
CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
return NULL;
}
logWrite("getContact(%I64d, \"%s\"): add contact.\n", contactId, contactName);
// записываем данные контакта
DBWriteContactSettingBlob(hContact, PLUGIN_PROTO_NAME, PLUGIN_DB_UID, &contactId, sizeof(contactId));
DBWriteContactSettingString(hContact, PLUGIN_PROTO_NAME, "Nick", contactName);
DBWriteContactSettingWord(hContact, PLUGIN_PROTO_NAME, "Status", ID_STATUS_ONLINE);

На этом базовая статья может считаться законченной.
Ссылки:
Мой Twitter-протокол
Плагины для Миранды, в том числе есть и с открытым кодом
Код Миранды

Теги:
рубрика Программирование
  • Похожие статьи
  • Предыдущие из рубрики