Примеры кода для PyXMPP и XMPPpy

Пока писал различные транспорты / сервисы для jabber’а – часто приходилось выискивать примеры кода, чтобы понять, какие блоки для чего использовать, чтобы вырисовать нужный XML. С PyXMPP было проще – под рукой было 2 проекта с неплохим объемом кода, на котором можно было учиться – наверное, поэтому мне больше PyXMPP и полюбился. С XMPPpy было сложнее, но в итоге разобрался.

В данной заметке – куски кода из моих проектов, реализующие ту или иную функцию. Код, в основном, скопирован “как есть” – разве что с удалением часто повторяющихся одинаковых блоков. Основная задача – показать последовательность действий, которая нужна для “рисования” нужного XML’а.

Первый пример – запрос статистики, XEP-0039: Statistics Gathering. Как указано мной в комментариях для PyXMPP, это неправильная реализация. В правильной должен быть запрос на список фич и дальше работа с этим списком – т.е., все общение идет в 2 шага и в итоге должен возвращаться только список запрошенных фич. По факту в ботах (а, фактически, такая статистика проверялась мной только на ботах) на запрос сразу возвращался полный список – в итоге и я так реализовывал для совместимости.

Сам список в довольно свободной форме – делается что-то типа категорий и значений.

XMPP stats example for PyXMPP

def get_stats(self, iq):
# It's incorrect implementation of XEP-0039, it should be in 2 steps (like search)
        iq = iq.make_result_response()
        q = iq.new_query("http://jabber.org/protocol/stats")

        upt = q.newChild(None, "stat", None)
        upt.setProp("name", 'time/uptime')
        upt.setProp("units", 'seconds')
        upt.setProp("value", str(int(time.time()) - self.start_time))

        reqsh = q.newChild(None, "stat", None)
        reqsh.setProp("name", 'messages/hourly')
        reqsh.setProp("units", 'messages')
        reqsh.setProp("value", str(hourly))

        self.stream.send(iq)
        return 1

В погодном транспорте я статистику не реализовывал, так что примера кода нет – но в целом, это несложно сделать по образцу остального кода.

Второй пример – последняя активность или запрос аптайма (в случае с сервером или транспортом) – XEP-0012: Last Activity.

Тут все довольно просто:

XMPP last example for PyXMPP

    def get_last(self, iq):
        iq = iq.make_result_response()
        q = iq.new_query("jabber:iq:last")
        q.setProp("seconds", str(int(time.time()) - self.start_time))
        self.stream.send(iq)
        return 1

XMPP last example for XMPPpy

    def iq_last_handler(self, iq):
        repl = iq.buildReply('result')
        query = xmpp.Node('query', attrs={'xmlns':xmpp.NS_LAST, 'seconds': (int(time.time() - self.last))})
        repl.setPayload([query])
        self.jabber.send(repl)
        raise NodeProcessed

Третий примерXEP-0199: XMPP Ping. Еще проще:

XMPP ping example for PyXMPP

def pingpong(self, iq):
        iq = iq.make_result_response()
        self.stream.send(iq)
        return 1

XMPP ping example for XMPPpy

    def iq_ping_handler(self, iq):
        repl = iq.buildReply('result')
        self.jabber.send(repl)
        raise NodeProcessed

ЧетвертыйXEP-0202: Entity Time.

XMPP time example for PyXMPP

    def get_time(self, iq):
        iq = iq.make_result_response()
        q = iq.xmlnode.newChild(None, "time", None)
        q.setProp("xmlns", "urn:xmpp:time")
        q.newTextChild(q.ns(), "tzo", "+02:00")
        q.newTextChild(q.ns(), "utc", time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
        self.stream.send(iq)
        return 1

XMPP time example for XMPPpy

    def iq_time_handler(self, iq):
        repl = iq.buildReply('result')
        query = Node('time')
        query.setTagData(tag='tzo',    val="+02:00")
        query.setTagData(tag='utc',    val=time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
        repl.setPayload([query])
        self.jabber.send(repl)
        raise NodeProcessed

ПятыйXEP-0054: vcard-temp. Фактически, в основном линейная XML’ка. Со всеми опциями можно ознакомиться в XEP’е. Вложенным параметров (из популярных) является только фото – по тому же принципу можно конструировать и другие многоуровневые данные.

В принципе, тем же кодом можно реализовать и XEP-0292: vCard4 Over XMPP – там немного отличается структура, но в основном – все то же.

XMPP vcard-temp example for PyXMPP

    def get_vCard(self,iq):

        iqmr=iq.make_result_response()
        q=iqmr.xmlnode.newChild(None,"vCard",None)
        q.setProp("xmlns","vcard-temp")

        transav=q.newTextChild(None,"PHOTO", None)
        transav.newTextChild(None, "BINVAL", self.gttlogo)
        transav.newTextChild(None, "TYPE", 'image/png')

        q.newTextChild(None,"BDAY","2022-12-07")
        q.newTextChild(None,"URL","https://github.com/jabberworld/gtt")

        if iq.get_to() == self.name:
            q.newTextChild(None,"FN","Google Translate Transport")
            q.newTextChild(None,"NICKNAME","Google Translate")
            q.newTextChild(None,"DESC","Google Translate Transport")
            q.newTextChild(None,"ROLE","Создаю ботов для получения перевода текста через Google Translate")
        self.stream.send(iqmr)
        return 1

XMPP vcard-temp example for XMPPpy

    def iq_vcard_handler(self, iq):
        repl = iq.buildReply('result')
        query = xmpp.Node('vCard', attrs={'xmlns':xmpp.NS_VCARD})

        if str(iq.getTo()) == self.domain:
            query.setTagData(tag='NICKNAME', val='Weather')
            query.setTagData(tag='FN',       val='Jabber Weather Transport')
            query.setTagData(tag='BDAY',     val='2022-07-22')
            query.setTagData(tag='DESC',     val='gismeteo.ru and meteonova.ru weather service')
            query.setTagData(tag='ROLE',     val='Создаю ботов для получения погоды с '+datasrc)
            query.setTagData(tag='URL',      val='https://github.com/jabberworld/gismeteo')

            transav = query.addChild('PHOTO')
            transav.setTagData(tag='BINVAL', val=self.weatherlogo)
            transav.setTagData(tag='TYPE',   val='image/png')

        repl.setPayload([query])
        self.jabber.send(repl)
        raise NodeProcessed

Шестой примерXEP-0092: Software Version. В варианте для XMPPpy чуть упрощенный пример, но разобраться несложно.

XMPP version example for PyXMPP

    def get_version(self,iq):
        global programmVersion
        iq=iq.make_result_response()
        q=iq.new_query("jabber:iq:version")
        q.newTextChild(q.ns(), "name", "Google Translate Transport")
        q.newTextChild(q.ns(), "version", programmVersion + " (" + str(len(self.LANGUAGES)) + " languages)")
        q.newTextChild(q.ns(), "os", "Python "+sys.version.split()[0]+" + PyXMPP")
        self.stream.send(iq)
        return 1

XMPP version example for XMPPpy

    def iq_version_handler(self, iq):
        name = Node('name')
        name.setData("Jabber Weather Service")
        version = Node('version')
        version.setData(self.version)

        repl = iq.buildReply('result')
        query = xmpp.Node('query', attrs={'xmlns':xmpp.NS_VERSION})
        query.addChild(node=name)
        query.addChild(node=version)
        repl.setPayload([query])
        self.jabber.send(repl)
        raise NodeProcessed

Ну и напоследок – работа с формами: XEP-0004: Data Forms – может быть полезно при реализации функции поиска или регистрации. Общение двухэтапное, поэтому сначала идет функция получения формы (get search или get register), а дальше на полученные поля и переключатели идет вторая передача данных с установленными параметрами – set search / set register – ну и возврат ответ от сервера. Возможно, этапов может быть больше (последовательное получение нескольких форм) – не знаю, не пробовал.

XMPP form example for PyXMPP

    def get_search(self,iq):
        iq=iq.make_result_response()
        q=iq.xmlnode.newChild(None,"query",None)
        q.setProp("xmlns","jabber:iq:search")
        q.newTextChild(None,"instructions","Enter a keyword")
        form=q.newChild(None,"x",None)
        form.setProp("xmlns","jabber:x:data")
        form.setProp("type","form")
        formType=form.newChild(None,"field",None)
        formType.setProp("type","hidden")
        formType.setProp("var","FORM_TYPE")
        formType.newTextChild(None,"value","jabber:iq:search")
        text=form.newChild(None,"field",None)
        text.setProp("type","text-single")
        text.setProp("label","Search")
        text.setProp("var","searchField")
        self.stream.send(iq)
        return 1

    def set_search(self, iq):
        fromjid = iq.get_from().bare()
        searchField=iq.xpath_eval("//r:field[@var='searchField']/r:value",{"r":"jabber:x:data"})
        if searchField:
            searchField='%'+searchField[0].getContent().replace("%","\\%")+'%'
        else:
            return
        if searchField=='%%' or len(searchField)<5:
            self.stream.send(iq.make_error_response("not-acceptable"))
            return
        self.dbCurST.execute("COMMIT")
        self.dbCurST.execute("SELECT feedname, description, url, subscribers, timeout FROM feeds WHERE (feedname LIKE %s OR description LIKE %s OR url LIKE %s OR tags LIKE %s) AND (private = '0' OR (private = '1' AND registrar = %s))", (searchField, searchField, searchField, searchField, fromjid))
        a=self.dbCurST.fetchall()

        print (a)

        iq=iq.make_result_response()
        q=iq.new_query("jabber:iq:search")

        form=q.newChild(None,"x",None)
        form.setProp("xmlns","jabber:x:data")
        form.setProp("type","result")

        formType=form.newChild(None,"field",None)
        formType.setProp("type","hidden")
        formType.setProp("var","FORM_TYPE")
        formType.newTextChild(None,"value","jabber:iq:search")

        reported=form.newChild(None,"reported",None)
        reportedJid=reported.newChild(None,"field",None)
        reportedJid.setProp("var","jid")
        reportedJid.setProp("label","JID")
        reportedJid.setProp("type","jid-single")

        reportedUrl=reported.newChild(None,"field",None)
        reportedUrl.setProp("var","url")
        reportedUrl.setProp("label","URL")
        reportedUrl.setProp("type","text-single")
	*****
        for d in a:
            item=form.newChild(None, "item", None)
            jidField=item.newChild(None, "field", None)
            jidField.setProp("var", "jid")
            jiddata = d[0]+"@"+self.name
            jidField.newTextChild(None, "value", jiddata.encode('utf-8'))

            urlField=item.newChild(None, "field", None)
            urlField.setProp("var", "url")
            urlField.newTextChild(None, "value", d[2].encode('utf-8'))
	*****

        self.stream.send(iq)
        return 1

XMPP form example for XMPPpy

    def iq_search_handler(self, iq):
        typ = iq.getType()
        iq_children = iq.getQueryChildren()
        if (typ=='get') and (not iq_children):
            repl = iq.buildReply('result')
            repl.setQueryPayload(self.get_register_form())
            self.jabber.send(repl)
            raise NodeProcessed
        elif (typ=='set') and iq_children:
            self.set_register_form(iq)
            raise NodeProcessed

    def get_register_form(self):
        ft = DataForm('form')
        ft.addChild(node=DataField(name='FORM_TYPE',value=NS_SEARCH, typ='hidden'))
        ft.addChild(node=DataField(name='city', label='Город', typ='text-single'))
        return [ft]

    def set_register_form(self, iq):
        iq_children = iq.getQueryChildren()
        for nod in iq_children:
            for k in nod.getChildren():
                if k.getAttr('var') == 'city':
                    for j in k.getChildren():
                        searchField = j.getData()
        if searchField:
            searchField='%'+searchField.replace("%","\\%")+'%'
        else:
            return
        if searchField=='%%' or len(searchField)<4:
            self.send_bad_request(iq)
            return
        data = cur.execute("SELECT * FROM cityindex WHERE idx LIKE (?) OR country_en LIKE (?) OR country_ru LIKE (?) OR name_en LIKE (?) OR name_ru LIKE (?) OR keywords LIKE (?) LIMIT 100", (searchField, searchField, searchField, searchField, searchField, searchField))
        data = cur.fetchall()
        print(data)

        repl = iq.buildReply('result')
        query = xmpp.Node('query', attrs={'xmlns':xmpp.NS_SEARCH})
        rprt = Node('reported', payload=[
            DataField(label='JID'       ,name='jid'  ,typ='jid-single'),
            DataField(label='Index'     ,name='idx'  ,typ='text-single'),
            DataField(label='Страна'    ,name='cru'  ,typ='text-single'),
            DataField(label='Регион'    ,name='rru'  ,typ='text-single'),
            DataField(label='Город'     ,name='nru'  ,typ='text-single'),
            DataField(label='Country'   ,name='cen'  ,typ='text-single'),
            DataField(label='Region'    ,name='ren'  ,typ='text-single'),
            DataField(label='City'      ,name='nen'  ,typ='text-single'),
            DataField(label='Lat.'      ,name='lat'  ,typ='text-single'),
            DataField(label='Long.'     ,name='lon'  ,typ='text-single'),
            DataField(label='Alt.'      ,name='alt'  ,typ='text-single'),
            DataField(label='Keywords'  ,name='kwr'  ,typ='text-single')])

        form = DataForm('result')
        form.addChild(node=DataField(name='FORM_TYPE', value=NS_SEARCH, typ='hidden'))
        form.addChild(node=rprt)

        for flds in data:
            rpl = Node('item', payload=[
                DataField(name='jid', value=JID(node=unicode(str(flds[0]), "utf-8"), domain=self.domain)),
                DataField(name='idx', value=flds[0]),
                DataField(name='cru', value=flds[1]),
                DataField(name='rru', value=flds[2]),
                DataField(name='cen', value=flds[3]),
                DataField(name='ren', value=flds[4]),
                DataField(name='nru', value=flds[6]),
                DataField(name='nen', value=flds[5]),
                DataField(name='lat', value=flds[7]),
                DataField(name='lon', value=flds[8]),
                DataField(name='alt', value=flds[9]),
                DataField(name='kwr', value=flds[10])])
            form.addChild(node=rpl)

        query.addChild(node=form)
        repl.setPayload([query])
        self.jabber.send(repl)

Также между этими библиотеками различается и разбор переданных параметров – xpath_eval в PyXMPP и проход по дереву в XMPPpy.

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

Заметки обо всем