Blog Trac Lyrics for music

Archive for October, 2007

Miranda + Python (MirPy)

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

Я использовал последнюю на данный момент версию - 0.1.3.1 (static), которая скомпилирована с поддержкой Python 2.5. Подключаем плагин к миранде, запускаем - MirPy показывает формочку, с полем для ввода и полем для вывода, куда перенаправляется весь обычный вывод. В нашем распоряжении - весь питон со всеми установленными расширениями, плюс модули MirPy, clist, contact, database, messaging, mirandamisc, popups, status. Какой-либо внешней документации я не нашёл, только три примера от автора плагина на miranda forum‘ах. Впрочем, я документацию особо и не искал, а воспользовался таким кусочком кода:

import MirPy
import database
from pprint import pprint
MirPy.ConsoleClear()

pprint(database.__dict__)
for k, v in database.__dict__.items():
    if v.__doc__:
        print k, '::\n', v.__doc__, '\n\n'

Он вывел всю информацию, которая мне впоследствии понадобилась для поставленной задачи.

Если доставать список контактов обычным способом (первый->следующий), то достаются только те контакты, которые и так видны, а затеряные в пучинах так и остаются неведомы. Поэтому я воспользовался брут-форсом и просто перебрал все возможные идентификаторы:

import MirPy
import database
MirPy.ConsoleClear()

# first -> next
contacts = []
contact = database.ContactFindFirst()
while contact:
    contacts.append(contact)
    contact = database.ContactFindNext(contacts)

print contacts

# brute-force
# needs several tens of seconds to execute
contacts = []
min_cont, max_cont = 0, 25000000
for contact in xrange(min_cont, max_cont):
    if database.ContactIs(contact):
        contacts_all.append(contact)

print contacts

Первым способом достало 141 контакт, вторым - 467. Дальше, собственно, можно получить всю историю, а значит, нужно разобраться с форматом событий. У события есть такие параметры: timestamp, flags, blob, module, eventType. Лезть в исходники Миранды было лень, и потому я просто поглядел на дамп сырых данных. Результаты:

  • timestamp - ясное дело, проблем не вызвал; это время события от Эпохи
  • blob - тело сообщения
  • module - строка, которая идентифицирует модуль (протокол). В моём случае - ‘ICQ’, ‘JABBER’.
  • eventType - тип события. 0 - сообщение, 1001 - запрос на авторизацию, 1002 - файл. Других не нашёл
  • flags - разные битовые флаги. Первый бит говорит о том, что это первое сообщение в истории, второй бит - исходящее сообщение, третий бит - исходящее, пятый - юникодное.
В результате исследований написался вот такой скриптик (он даже чуть-чуть конфигурируется!):

import database
from datetime import datetime

DIRECTORY = 'd:\\history\\'
DIRECTORY_PROCESSED = 'd:\\history\\processed\\'
FORMAT_TIME = '%Y-%m-%d %H:%M:%S'

contacts = [9301095]

for contact in contacts:
    event = database.EventFindFirst(contact)
    if not event:
        continue
    f = open('%s%d.txt' % (DIRECTORY, contact), 'w')
    f_p = open('%s%d.txt' % (DIRECTORY_PROCESSED, contact), 'w')
    event_obj = database.EventGet(event)
    f_p.write('Module: %s\n\n' % event_obj.module)
    while event:
        event_obj = database.EventGet(event)
        f.write(str(event_obj.__dict__))
        f.write('\n')
        event_time = datetime.fromtimestamp(event_obj.timestamp).strftime(FORMAT_TIME)
        is_utf8 = event_obj.flags & 16
        direction = 'IN' if event_obj.flags & 4 else 'OUT'
        # first_message = event_obj.flags & 1

        try:
            event_body = event_obj.blob.decode('utf8' if is_utf8 else 'cp1251')
            processed = u'%s %s\n%s\n\n' % (direction, event_time, event_body)
            f_p.write(processed.encode('utf8'))
        except:
            f_p.write('%s %s\n### ERROR ###\n%s\n\n' % (direction, event_time, event_obj.blob))
        event = database.EventFindNext(event)
    f.close()
    f_p.close()

Список контактов надо положить в contacts.

P.S.На всю мою историю нашлось три вхождения ### ERROR ###. Не знаю в чём причина, может вуглускр покоцал три сообщения :)
P.P.S.Версия миранды - 0.7.1.