.*?)$", - r"''(?P.+?)''", - r"__(?P .+?)__", - r"%%%(?P )", - r"\+\+(?P .+?)\+\+", - r"--(?P .+?)--", - r"\[(?P.+?)\]", - r"\(\((?P .+?)\)\)", - r"~(?P .+?)~", - r"\?\?(?P.+?\|.+?)\?\?", - r"{{(?P .+?)}}", - r"@@(?P .+?)@@", - r"\$\$(?P.+?)\$\$", - r"(?P .+?)", - ] +wiki = [ + r"\\" + ESCAPE_CHARS_BASE.format(ESCAPE_CHARS_EXTRA), + r"^!!!!!(?P .+?)$", + r"^!!!!(?P .+?)$", + r"^!!!(?P .+?)$", + r"^!!(?P .+?)$", + r"^!(?P .+?)$", + r"^----$(?P )", + r"^\*(?P .*?)$", + r"^#(?P .*?)$", + r"^ (?P .*?)$", + r"^> +?(?P .*?)$", + r"''(?P.+?)''", + r"__(?P .+?)__", + r"%%%(?P )", + r"\+\+(?P .+?)\+\+", + r"--(?P .+?)--", + r"\[(?P.+?)\]", + r"\(\((?P .+?)\)\)", + r"~(?P .+?)~", + r"\?\?(?P.+?\|.+?)\?\?", + r"{{(?P .+?)}}", + r"@@(?P .+?)@@", + r"\$\$(?P.+?)\$\$", + r"(?P .+?)", +] -wiki_re = re.compile('|'.join(wiki), re.MULTILINE | re.DOTALL) -wiki_block_level_re = re.compile(r"^///html(?P.+?)///\n\n|(?P .+?)(?:\n{2,}|\Z)", re.MULTILINE | re.DOTALL) +wiki_re = re.compile("|".join(wiki), re.MULTILINE | re.DOTALL) +wiki_block_level_re = re.compile( + r"^///html(?P.+?)///\n\n|(?P .+?)(?:\n{2,}|\Z)", + re.MULTILINE | re.DOTALL, +) class DCWikiParser(object): - def __init__(self): self._footnotes = None for i in xrange(5): - setattr(self, - 'parser_h{}_title'.format(i), - lambda string, parent, i=i: self._parser_title(string, parent, 'h{}'.format(i))) + setattr( + self, + "parser_h{}_title".format(i), + lambda string, parent, i=i: self._parser_title( + string, parent, "h{}".format(i) + ), + ) def parser_paragraph(self, string, parent): - p_elt = parent.addElement('p') + p_elt = parent.addElement("p") self._parse(string, p_elt) def parser_html(self, string, parent): @@ -101,7 +111,7 @@ class DCWikiParser(object): log.warning(u"Error while parsing HTML content, ignoring it: {}".format(e)) return children = list(div_elt.elements()) - if len(children) == 1 and children[0].name == 'div': + if len(children) == 1 and children[0].name == "div": div_elt = children[0] parent.addChild(div_elt) @@ -113,128 +123,130 @@ class DCWikiParser(object): elt.addContent(string) def parser_horizontal_rule(self, string, parent): - parent.addElement('hr') + parent.addElement("hr") def _parser_list(self, string, parent, list_type): depth = 0 - while string[depth:depth+1] == '*': - depth +=1 + while string[depth : depth + 1] == "*": + depth += 1 string = string[depth:].lstrip() - for i in xrange(depth+1): + for i in xrange(depth + 1): list_elt = getattr(parent, list_type) if not list_elt: parent = parent.addElement(list_type) else: parent = list_elt - li_elt = parent.addElement('li') + li_elt = parent.addElement("li") self._parse(string, li_elt) def parser_list_bullet(self, string, parent): - self._parser_list(string, parent, 'ul') + self._parser_list(string, parent, "ul") def parser_list_ordered(self, string, parent): - self._parser_list(string, parent, 'ol') + self._parser_list(string, parent, "ol") def parser_preformated(self, string, parent): pre_elt = parent.pre if pre_elt is None: - pre_elt = parent.addElement('pre') + pre_elt = parent.addElement("pre") else: # we are on a new line, and this is important for - pre_elt.addContent('\n') + pre_elt.addContent("\n") pre_elt.addContent(string) def parser_quote(self, string, parent): blockquote_elt = parent.blockquote if blockquote_elt is None: - blockquote_elt = parent.addElement('blockquote') + blockquote_elt = parent.addElement("blockquote") p_elt = blockquote_elt.p if p_elt is None: - p_elt = blockquote_elt.addElement('p') + p_elt = blockquote_elt.addElement("p") else: - string = u'\n' + string + string = u"\n" + string self._parse(string, p_elt) def parser_emphasis(self, string, parent): - em_elt = parent.addElement('em') + em_elt = parent.addElement("em") self._parse(string, em_elt) def parser_strong_emphasis(self, string, parent): - strong_elt = parent.addElement('strong') + strong_elt = parent.addElement("strong") self._parse(string, strong_elt) def parser_line_break(self, string, parent): - parent.addElement('br') + parent.addElement("br") def parser_insertion(self, string, parent): - ins_elt = parent.addElement('ins') + ins_elt = parent.addElement("ins") self._parse(string, ins_elt) def parser_deletion(self, string, parent): - del_elt = parent.addElement('del') + del_elt = parent.addElement("del") self._parse(string, del_elt) def parser_link(self, string, parent): - url_data = string.split(u'|') - a_elt = parent.addElement('a') + url_data = string.split(u"|") + a_elt = parent.addElement("a") length = len(url_data) if length == 1: url = url_data[0] - a_elt['href'] = url + a_elt["href"] = url a_elt.addContent(url) else: name = url_data[0] url = url_data[1] - a_elt['href'] = url + a_elt["href"] = url a_elt.addContent(name) if length >= 3: - a_elt['lang'] = url_data[2] + a_elt["lang"] = url_data[2] if length >= 4: - a_elt['title'] = url_data[3] + a_elt["title"] = url_data[3] if length > 4: log.warning(u"too much data for url, ignoring extra data") def parser_image(self, string, parent): - image_data = string.split(u'|') - img_elt = parent.addElement('img') + image_data = string.split(u"|") + img_elt = parent.addElement("img") - for idx, attribute in enumerate(('src', 'alt', 'position', 'longdesc')): + for idx, attribute in enumerate(("src", "alt", "position", "longdesc")): try: data = image_data[idx] except IndexError: break - if attribute != 'position': + if attribute != "position": img_elt[attribute] = data else: data = data.lower() - if data in ('l', 'g'): - img_elt['style'] = "display:block; float:left; margin:0 1em 1em 0" - elif data in ('r', 'd'): - img_elt['style'] = "display:block; float:right; margin:0 0 1em 1em" - elif data == 'c': - img_elt['style'] = "display:block; margin-left:auto; margin-right:auto" + if data in ("l", "g"): + img_elt["style"] = "display:block; float:left; margin:0 1em 1em 0" + elif data in ("r", "d"): + img_elt["style"] = "display:block; float:right; margin:0 0 1em 1em" + elif data == "c": + img_elt[ + "style" + ] = "display:block; margin-left:auto; margin-right:auto" else: log.warning(u"bad position argument for image, ignoring it") def parser_anchor(self, string, parent): - a_elt = parent.addElement('a') - a_elt['id'] = string + a_elt = parent.addElement("a") + a_elt["id"] = string def parser_acronym(self, string, parent): - acronym, title = string.split(u'|',1) - acronym_elt = parent.addElement('acronym', content=acronym) - acronym_elt['title'] = title + acronym, title = string.split(u"|", 1) + acronym_elt = parent.addElement("acronym", content=acronym) + acronym_elt["title"] = title def parser_inline_quote(self, string, parent): - quote_data = string.split(u'|') + quote_data = string.split(u"|") quote = quote_data[0] - q_elt = parent.addElement('q', content=quote) - for idx, attribute in enumerate(('lang', 'cite'), 1): + q_elt = parent.addElement("q", content=quote) + for idx, attribute in enumerate(("lang", "cite"), 1): try: data = quote_data[idx] except IndexError: @@ -242,21 +254,21 @@ class DCWikiParser(object): q_elt[attribute] = data def parser_code(self, string, parent): - parent.addElement('code', content=string) + parent.addElement("code", content=string) def parser_footnote(self, string, parent): idx = len(self._footnotes) + 1 note_txt = NOTE_TPL.format(idx) - sup_elt = parent.addElement('sup') - sup_elt['class'] = 'note' - a_elt = sup_elt.addElement('a', content=note_txt) - a_elt['id'] = NOTE_A_REV_TPL.format(idx) - a_elt['href'] = u'#{}'.format(NOTE_A_TPL.format(idx)) + sup_elt = parent.addElement("sup") + sup_elt["class"] = "note" + a_elt = sup_elt.addElement("a", content=note_txt) + a_elt["id"] = NOTE_A_REV_TPL.format(idx) + a_elt["href"] = u"#{}".format(NOTE_A_TPL.format(idx)) - p_elt = domish.Element((None, 'p')) - a_elt = p_elt.addElement('a', content=note_txt) - a_elt['id'] = NOTE_A_TPL.format(idx) - a_elt['href'] = u'#{}'.format(NOTE_A_REV_TPL.format(idx)) + p_elt = domish.Element((None, "p")) + a_elt = p_elt.addElement("a", content=note_txt) + a_elt["id"] = NOTE_A_TPL.format(idx) + a_elt["href"] = u"#{}".format(NOTE_A_REV_TPL.format(idx)) self._parse(string, p_elt) # footnotes are actually added at the end of the parsing self._footnotes.append(p_elt) @@ -273,7 +285,7 @@ class DCWikiParser(object): return matched = match.group(match.lastgroup) try: - parser = getattr(self, 'parser_{}'.format(match.lastgroup)) + parser = getattr(self, "parser_{}".format(match.lastgroup)) except AttributeError: log.warning(u"No parser found for {}".format(match.lastgroup)) # parent.addContent(string) @@ -282,46 +294,46 @@ class DCWikiParser(object): def parse(self, string): self._footnotes = [] - div_elt = domish.Element((None, 'div')) + div_elt = domish.Element((None, "div")) self._parse(string, parent=div_elt, block_level=True) if self._footnotes: - foot_div_elt = div_elt.addElement('div') - foot_div_elt['class'] = 'footnotes' + foot_div_elt = div_elt.addElement("div") + foot_div_elt["class"] = "footnotes" # we add a simple horizontal rule which can be customized # with footnotes class, instead of a text which would need # to be translated - foot_div_elt.addElement('hr') + foot_div_elt.addElement("hr") for elt in self._footnotes: foot_div_elt.addChild(elt) return div_elt class XHTMLParser(object): - def __init__(self): self.flags = None self.toto = 0 - self.footnotes = None # will hold a map from url to buffer id - for i in xrange(1,6): - setattr(self, - 'parser_h{}'.format(i), - lambda elt, buf, level=i: self.parserHeading(elt, buf, level) - ) + self.footnotes = None # will hold a map from url to buffer id + for i in xrange(1, 6): + setattr( + self, + "parser_h{}".format(i), + lambda elt, buf, level=i: self.parserHeading(elt, buf, level), + ) def parser_a(self, elt, buf): try: - url = elt['href'] + url = elt["href"] except KeyError: # probably an anchor try: - id_ = elt['id'] + id_ = elt["id"] if not id_: # we don't want empty values raise KeyError except KeyError: self.parserGeneric(elt, buf) else: - buf.append(u'~~{}~~'.format(id_)) + buf.append(u"~~{}~~".format(id_)) return link_data = [url] @@ -329,52 +341,54 @@ class XHTMLParser(object): if name != url: link_data.insert(0, name) - lang = elt.getAttribute('lang') - title = elt.getAttribute('title') + lang = elt.getAttribute("lang") + title = elt.getAttribute("title") if lang is not None: link_data.append(lang) elif title is not None: - link_data.appand(u'') + link_data.appand(u"") if title is not None: link_data.append(title) - buf.append(u'[') - buf.append(u'|'.join(link_data)) - buf.append(u']') + buf.append(u"[") + buf.append(u"|".join(link_data)) + buf.append(u"]") def parser_acronym(self, elt, buf): try: - title = elt['title'] + title = elt["title"] except KeyError: log.debug(u"Acronyme without title, using generic parser") self.parserGeneric(elt, buf) return - buf.append(u'??{}|{}??'.format(unicode(elt), title)) + buf.append(u"??{}|{}??".format(unicode(elt), title)) def parser_blockquote(self, elt, buf): # we remove wrapping to avoid empty line with "> " - children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')]) - if len(children) == 1 and children[0].name == 'p': + children = list( + [child for child in elt.children if unicode(child).strip() not in ("", "\n")] + ) + if len(children) == 1 and children[0].name == "p": elt = children[0] tmp_buf = [] self.parseChildren(elt, tmp_buf) - blockquote = u'> ' + u'\n> '.join(u''.join(tmp_buf).split('\n')) + blockquote = u"> " + u"\n> ".join(u"".join(tmp_buf).split("\n")) buf.append(blockquote) def parser_br(self, elt, buf): - buf.append(u'%%%') + buf.append(u"%%%") def parser_code(self, elt, buf): - buf.append(u'@@') + buf.append(u"@@") self.parseChildren(elt, buf) - buf.append(u'@@') + buf.append(u"@@") def parser_del(self, elt, buf): - buf.append(u'--') + buf.append(u"--") self.parseChildren(elt, buf) - buf.append(u'--') + buf.append(u"--") def parser_div(self, elt, buf): - if elt.getAttribute('class') == 'footnotes': + if elt.getAttribute("class") == "footnotes": self.parserFootnote(elt, buf) else: self.parseChildren(elt, buf, block=True) @@ -387,56 +401,56 @@ class XHTMLParser(object): def parser_h6(self, elt, buf): # XXX:
heading is not managed by wiki syntax # so we handle it with a - elt = copy.copy(elt) # we don't want to change to original element - elt.name = 'h5' + elt = copy.copy(elt) # we don't want to change to original element + elt.name = "h5" self._parse(elt, buf) def parser_hr(self, elt, buf): - buf.append(u'\n----\n') + buf.append(u"\n----\n") def parser_img(self, elt, buf): try: - url = elt['src'] + url = elt["src"] except KeyError: log.warning(u"Ignoringwithout src") return - image_data=[url] + image_data = [url] - alt = elt.getAttribute('alt') - style = elt.getAttribute('style', '') - desc = elt.getAttribute('longdesc') + alt = elt.getAttribute("alt") + style = elt.getAttribute("style", "") + desc = elt.getAttribute("longdesc") - if '0 1em 1em 0' in style: - position = 'L' - elif '0 0 1em 1em' in style: - position = 'R' - elif 'auto' in style: - position = 'C' + if "0 1em 1em 0" in style: + position = "L" + elif "0 0 1em 1em" in style: + position = "R" + elif "auto" in style: + position = "C" else: position = None if alt: image_data.append(alt) elif position or desc: - image_data.append(u'') + image_data.append(u"") if position: image_data.append(position) elif desc: - image_data.append(u'') + image_data.append(u"") if desc: image_data.append(desc) - buf.append(u'((') - buf.append(u'|'.join(image_data)) - buf.append(u'))') + buf.append(u"((") + buf.append(u"|".join(image_data)) + buf.append(u"))") def parser_ins(self, elt, buf): - buf.append(u'++') + buf.append(u"++") self.parseChildren(elt, buf) - buf.append(u'++') + buf.append(u"++") def parser_li(self, elt, buf): flag = None @@ -447,11 +461,11 @@ class XHTMLParser(object): if current_flag is None: current_flag = flag if flag == current_flag: - bullets.append(u'*' if flag == FLAG_UL else u'#') + bullets.append(u"*" if flag == FLAG_UL else u"#") else: break - if flag != current_flag and buf[-1] == u' ': + if flag != current_flag and buf[-1] == u" ": # this trick is to avoid a space when we switch # from (un)ordered to the other type on the same row # e.g. *# unorder + ordered item @@ -459,64 +473,75 @@ class XHTMLParser(object): buf.extend(bullets) - buf.append(u' ') + buf.append(u" ") self.parseChildren(elt, buf) - buf.append(u'\n') + buf.append(u"\n") def parser_ol(self, elt, buf): self.parserList(elt, buf, FLAG_OL) def parser_p(self, elt, buf): self.parseChildren(elt, buf) - buf.append(u'\n\n') + buf.append(u"\n\n") def parser_pre(self, elt, buf): - pre = u''.join([child.toXml() if domish.IElement.providedBy(child) else unicode(child) for child in elt.children]) - pre = u' ' + u'\n '.join(pre.split('\n')) + pre = u"".join( + [ + child.toXml() if domish.IElement.providedBy(child) else unicode(child) + for child in elt.children + ] + ) + pre = u" " + u"\n ".join(pre.split("\n")) buf.append(pre) def parser_q(self, elt, buf): - quote_data=[unicode(elt)] + quote_data = [unicode(elt)] - lang = elt.getAttribute('lang') - cite = elt.getAttribute('url') + lang = elt.getAttribute("lang") + cite = elt.getAttribute("url") if lang: quote_data.append(lang) elif cite: - quote_data.append(u'') + quote_data.append(u"") if cite: quote_data.append(cite) - buf.append(u'{{') - buf.append(u'|'.join(quote_data)) - buf.append(u'}}') + buf.append(u"{{") + buf.append(u"|".join(quote_data)) + buf.append(u"}}") def parser_span(self, elt, buf): self.parseChildren(elt, buf, block=True) def parser_strong(self, elt, buf): - buf.append(u'__') + buf.append(u"__") self.parseChildren(elt, buf) - buf.append(u'__') + buf.append(u"__") def parser_sup(self, elt, buf): # sup is mainly used for footnotes, so we check if we have an anchor inside - children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')]) - if (len(children) == 1 and domish.IElement.providedBy(children[0]) - and children[0].name == 'a' and '#' in children[0].getAttribute('href', '')): - url = children[0]['href'] - note_id = url[url.find('#')+1:] + children = list( + [child for child in elt.children if unicode(child).strip() not in ("", "\n")] + ) + if ( + len(children) == 1 + and domish.IElement.providedBy(children[0]) + and children[0].name == "a" + and "#" in children[0].getAttribute("href", "") + ): + url = children[0]["href"] + note_id = url[url.find("#") + 1 :] if not note_id: log.warning("bad link found in footnote") self.parserGeneric(elt, buf) return # this looks like a footnote - buf.append(u'$$') - buf.append(u' ') # placeholder + buf.append(u"$$") + buf.append(u" ") # placeholder self.footnotes[note_id] = len(buf) - 1 - buf.append(u'$$') + buf.append(u"$$") else: self.parserGeneric(elt, buf) @@ -537,39 +562,41 @@ class XHTMLParser(object): raise exceptions.InternalError(u"flag has been removed by an other parser") def parserHeading(self, elt, buf, level): - buf.append((6-level) * u'!') + buf.append((6 - level) * u"!") for child in elt.children: # we ignore other elements for a Hx title self.parserText(child, buf) - buf.append(u'\n') + buf.append(u"\n") def parserFootnote(self, elt, buf): for elt in elt.elements(): # all children other than are ignored - if elt.name == 'p': + if elt.name == "p": a_elt = elt.a if a_elt is None: - log.warning(u" element doesn't contain in footnote, ignoring it") + log.warning( + u" element doesn't contain in footnote, ignoring it" + ) continue try: - note_idx = self.footnotes[a_elt['id']] + note_idx = self.footnotes[a_elt["id"]] except KeyError: log.warning(u"Note id doesn't match any known note, ignoring it") # we create a dummy element to parse all children after the - dummy_elt = domish.Element((None, 'note')) + dummy_elt = domish.Element((None, "note")) a_idx = elt.children.index(a_elt) - dummy_elt.children = elt.children[a_idx+1:] + dummy_elt.children = elt.children[a_idx + 1 :] note_buf = [] self.parseChildren(dummy_elt, note_buf) # now we can replace the placeholder - buf[note_idx] = u''.join(note_buf) + buf[note_idx] = u"".join(note_buf) def parserText(self, txt, buf, keep_whitespaces=False): txt = unicode(txt) if not keep_whitespaces: # we get text and only let one inter word space - txt = u' '.join(txt.split()) - txt = re.sub(ESCAPE_CHARS, r'\\\1', txt) + txt = u" ".join(txt.split()) + txt = re.sub(ESCAPE_CHARS, r"\\\1", txt) if txt: buf.append(txt) return txt @@ -582,9 +609,9 @@ class XHTMLParser(object): def parseChildren(self, elt, buf, block=False): first_visible = True for child in elt.children: - if not block and not first_visible and buf and buf[-1][-1] not in (' ','\n'): + if not block and not first_visible and buf and buf[-1][-1] not in (" ", "\n"): # we add separation if it isn't already there - buf.append(u' ') + buf.append(u" ") if domish.IElement.providedBy(child): self._parse(child, buf) first_visible = False @@ -595,7 +622,7 @@ class XHTMLParser(object): def _parse(self, elt, buf): elt_name = elt.name.lower() - style = elt.getAttribute('style') + style = elt.getAttribute("style") if style and elt_name not in ELT_WITH_STYLE: # if we have style we use generic parser to put raw HTML # to avoid losing it @@ -604,7 +631,9 @@ class XHTMLParser(object): try: parser = getattr(self, "parser_{}".format(elt_name)) except AttributeError: - log.debug("Can't find parser for {} element, using generic one".format(elt.name)) + log.debug( + "Can't find parser for {} element, using generic one".format(elt.name) + ) parser = self.parserGeneric parser(elt, buf) @@ -613,7 +642,7 @@ class XHTMLParser(object): self.footnotes = {} buf = [] self._parse(elt, buf) - return u''.join(buf) + return u"".join(buf) def parseString(self, string): wrapped_html = u"
{}".format(string) @@ -623,7 +652,7 @@ class XHTMLParser(object): log.warning(u"Error while parsing HTML content: {}".format(e)) return children = list(div_elt.elements()) - if len(children) == 1 and children[0].name == 'div': + if len(children) == 1 and children[0].name == "div": div_elt = children[0] return self.parse(div_elt) @@ -637,7 +666,9 @@ class DCWikiSyntax(object): self._dc_parser = DCWikiParser() self._xhtml_parser = XHTMLParser() self._stx = self.host.plugins["TEXT-SYNTAXES"] - self._stx.addSyntax(self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD]) + self._stx.addSyntax( + self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD] + ) def parseWiki(self, wiki_stx): div_elt = self._dc_parser.parse(wiki_stx) diff --git a/sat/plugins/plugin_tickets_import.py b/sat/plugins/plugin_tickets_import.py index 77fadc18..4d179299 100755 --- a/sat/plugins/plugin_tickets_import.py +++ b/sat/plugins/plugin_tickets_import.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import defer from sat.tools.common import uri @@ -34,15 +35,17 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"], C.PI_MAIN: "TicketsImportPlugin", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _(u"""Tickets import management: -This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""") + C.PI_DESCRIPTION: _( + u"""Tickets import management: +This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""" + ), } -OPT_MAPPING = 'mapping' -FIELDS_LIST = (u'labels', u'cc_emails') # fields which must have a list as value -FIELDS_DATE = (u'created', u'updated') +OPT_MAPPING = "mapping" +FIELDS_LIST = (u"labels", u"cc_emails") # fields which must have a list as value +FIELDS_DATE = (u"created", u"updated") -NS_TICKETS = 'org.salut-a-toi.tickets:0' +NS_TICKETS = "org.salut-a-toi.tickets:0" class TicketsImportPlugin(object): @@ -54,13 +57,15 @@ class TicketsImportPlugin(object): log.info(_("plugin Tickets Import initialization")) self.host = host self._importers = {} - self._p = host.plugins['XEP-0060'] - self._m = host.plugins['XEP-0277'] - self._s = host.plugins['PUBSUB_SCHEMA'] - host.plugins['IMPORT'].initialize(self, u'tickets') + self._p = host.plugins["XEP-0060"] + self._m = host.plugins["XEP-0277"] + self._s = host.plugins["PUBSUB_SCHEMA"] + host.plugins["IMPORT"].initialize(self, u"tickets") @defer.inlineCallbacks - def importItem(self, client, item_import_data, session, options, return_data, service, node): + def importItem( + self, client, item_import_data, session, options, return_data, service, node + ): """ @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored. @@ -101,20 +106,24 @@ class TicketsImportPlugin(object): If you specify several import ticket key to the same dest key, the values will be joined with line feeds """ - if 'comments_uri' in item_import_data: - raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer')) + if "comments_uri" in item_import_data: + raise exceptions.DataError( + _(u"comments_uri key will be generated and must not be used by importer") + ) for key in FIELDS_LIST: if not isinstance(item_import_data.get(key, []), list): - raise exceptions.DataError(_(u'{key} must be a list').format(key=key)) + raise exceptions.DataError(_(u"{key} must be a list").format(key=key)) for key in FIELDS_DATE: try: item_import_data[key] = utils.xmpp_date(item_import_data[key]) except KeyError: continue - if session[u'root_node'] is None: - session[u'root_node'] = NS_TICKETS - if not 'schema' in session: - session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node']) + if session[u"root_node"] is None: + session[u"root_node"] = NS_TICKETS + if not "schema" in session: + session["schema"] = yield self._s.getSchemaForm( + client, service, node or session[u"root_node"] + ) defer.returnValue(item_import_data) @defer.inlineCallbacks @@ -122,40 +131,53 @@ class TicketsImportPlugin(object): # TODO: force "open" permission (except if private, check below) # TODO: handle "private" metadata, to have non public access for node # TODO: node access/publish model should be customisable - comments = ticket_data.get('comments', []) + comments = ticket_data.get("comments", []) service = yield self._m.getCommentsService(client) - node = self._m.getCommentsNode(session['root_node'] + u'_' + ticket_data['id']) - node_options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, - self._p.OPT_PERSIST_ITEMS: 1, - self._p.OPT_MAX_ITEMS: -1, - self._p.OPT_DELIVER_PAYLOADS: 1, - self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, - self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, - } + node = self._m.getCommentsNode(session["root_node"] + u"_" + ticket_data["id"]) + node_options = { + self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, + } yield self._p.createIfNewNode(client, service, node, options=node_options) - ticket_data['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=service.full(), node=node) + ticket_data["comments_uri"] = uri.buildXMPPUri( + u"pubsub", subtype="microblog", path=service.full(), node=node + ) for comment in comments: - if 'updated' not in comment and 'published' in comment: + if "updated" not in comment and "published" in comment: # we don't want an automatic update date - comment['updated'] = comment['published'] + comment["updated"] = comment["published"] yield self._m.send(client, comment, service, node) def publishItem(self, client, ticket_data, service, node, session): if node is None: node = NS_TICKETS - id_ = ticket_data.pop('id', None) - log.debug(u"uploading item [{id}]: {title}".format(id=id_, title=ticket_data.get('title',''))) - return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_) + id_ = ticket_data.pop("id", None) + log.debug( + u"uploading item [{id}]: {title}".format( + id=id_, title=ticket_data.get("title", "") + ) + ) + return self._s.sendDataFormItem( + client, service, node, ticket_data, session["schema"], id_ + ) def itemFilters(self, client, ticket_data, session, options): mapping = options.get(OPT_MAPPING) if mapping is not None: if not isinstance(mapping, dict): - raise exceptions.DataError(_(u'mapping option must be a dictionary')) + raise exceptions.DataError(_(u"mapping option must be a dictionary")) for source, dest in mapping.iteritems(): if not isinstance(source, unicode) or not isinstance(dest, unicode): - raise exceptions.DataError(_(u'keys and values of mapping must be sources and destinations ticket fields')) + raise exceptions.DataError( + _( + u"keys and values of mapping must be sources and destinations ticket fields" + ) + ) if source in ticket_data: value = ticket_data.pop(source) if dest in FIELDS_LIST: @@ -163,6 +185,6 @@ class TicketsImportPlugin(object): values.append(value) else: if dest in ticket_data: - ticket_data[dest] = ticket_data[dest] + u'\n' + value + ticket_data[dest] = ticket_data[dest] + u"\n" + value else: ticket_data[dest] = value diff --git a/sat/plugins/plugin_tickets_import_bugzilla.py b/sat/plugins/plugin_tickets_import_bugzilla.py index 410e3f6d..68f6f712 100755 --- a/sat/plugins/plugin_tickets_import_bugzilla.py +++ b/sat/plugins/plugin_tickets_import_bugzilla.py @@ -20,8 +20,10 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions + # from twisted.internet import threads from twisted.internet import defer import os.path @@ -36,25 +38,27 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["TICKETS_IMPORT"], C.PI_MAIN: "BugzillaImport", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla""") + C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla"""), } SHORT_DESC = D_(u"import tickets from Bugzilla xml export file") -LONG_DESC = D_(u"""This importer handle Bugzilla xml export file. +LONG_DESC = D_( + u"""This importer handle Bugzilla xml export file. To use it, you'll need to export tickets using XML. Tickets will be uploaded with the same ID as for Bugzilla, any existing ticket with this ID will be replaced. location: you must use the absolute path to your .xml file -""") +""" +) STATUS_MAP = { - 'NEW': 'queued', - 'ASSIGNED': 'started', - 'RESOLVED': 'review', - 'CLOSED': 'closed', - 'REOPENED': 'started' # we loose data here because there is no need on basic workflow to have a reopened status + "NEW": "queued", + "ASSIGNED": "started", + "RESOLVED": "review", + "CLOSED": "closed", + "REOPENED": "started", # we loose data here because there is no need on basic workflow to have a reopened status } @@ -65,67 +69,73 @@ class BugzillaParser(object): tickets = [] root = etree.parse(file_path) - for bug in root.xpath('bug'): + for bug in root.xpath("bug"): ticket = {} - ticket['id'] = bug.findtext('bug_id') - ticket['created'] = date_utils.date_parse(bug.findtext('creation_ts')) - ticket['updated'] = date_utils.date_parse(bug.findtext('delta_ts')) - ticket['title'] = bug.findtext('short_desc') - reporter_elt = bug.find('reporter') - ticket['author'] = reporter_elt.get('name') - if ticket['author'] is None: - if '@' in reporter_elt.text: - ticket['author'] = reporter_elt.text[:reporter_elt.text.find('@')].title() + ticket["id"] = bug.findtext("bug_id") + ticket["created"] = date_utils.date_parse(bug.findtext("creation_ts")) + ticket["updated"] = date_utils.date_parse(bug.findtext("delta_ts")) + ticket["title"] = bug.findtext("short_desc") + reporter_elt = bug.find("reporter") + ticket["author"] = reporter_elt.get("name") + if ticket["author"] is None: + if "@" in reporter_elt.text: + ticket["author"] = reporter_elt.text[ + : reporter_elt.text.find("@") + ].title() else: - ticket['author'] = u'no name' - ticket['author_email'] = reporter_elt.text - assigned_to_elt = bug.find('assigned_to') - ticket['assigned_to_name'] = assigned_to_elt.get('name') - ticket['assigned_to_email'] = assigned_to_elt.text - ticket['cc_emails'] = [e.text for e in bug.findall('cc')] - ticket['priority'] = bug.findtext('priority').lower().strip() - ticket['severity'] = bug.findtext('bug_severity').lower().strip() - ticket['product'] = bug.findtext('product') - ticket['component'] = bug.findtext('component') - ticket['version'] = bug.findtext('version') - ticket['platform'] = bug.findtext('rep_platform') - ticket['os'] = bug.findtext('op_sys') - ticket['status'] = STATUS_MAP.get(bug.findtext('bug_status'), 'queued') - ticket['milestone'] = bug.findtext('target_milestone') - + ticket["author"] = u"no name" + ticket["author_email"] = reporter_elt.text + assigned_to_elt = bug.find("assigned_to") + ticket["assigned_to_name"] = assigned_to_elt.get("name") + ticket["assigned_to_email"] = assigned_to_elt.text + ticket["cc_emails"] = [e.text for e in bug.findall("cc")] + ticket["priority"] = bug.findtext("priority").lower().strip() + ticket["severity"] = bug.findtext("bug_severity").lower().strip() + ticket["product"] = bug.findtext("product") + ticket["component"] = bug.findtext("component") + ticket["version"] = bug.findtext("version") + ticket["platform"] = bug.findtext("rep_platform") + ticket["os"] = bug.findtext("op_sys") + ticket["status"] = STATUS_MAP.get(bug.findtext("bug_status"), "queued") + ticket["milestone"] = bug.findtext("target_milestone") body = None comments = [] - for longdesc in bug.findall('long_desc'): + for longdesc in bug.findall("long_desc"): if body is None: - body = longdesc.findtext('thetext') + body = longdesc.findtext("thetext") else: - who = longdesc.find('who') - comment = {'id': longdesc.findtext('commentid'), - 'author_email': who.text, - 'published': date_utils.date_parse(longdesc.findtext('bug_when')), - 'author': who.get('name', who.text), - 'content': longdesc.findtext('thetext')} + who = longdesc.find("who") + comment = { + "id": longdesc.findtext("commentid"), + "author_email": who.text, + "published": date_utils.date_parse(longdesc.findtext("bug_when")), + "author": who.get("name", who.text), + "content": longdesc.findtext("thetext"), + } comments.append(comment) - ticket['body'] = body - ticket['comments'] = comments + ticket["body"] = body + ticket["comments"] = comments tickets.append(ticket) - tickets.sort(key = lambda t: int(t['id'])) + tickets.sort(key=lambda t: int(t["id"])) return (tickets, len(tickets)) class BugzillaImport(object): - def __init__(self, host): log.info(_(u"Bugilla Import plugin initialization")) self.host = host - host.plugins['TICKETS_IMPORT'].register('bugzilla', self.Import, SHORT_DESC, LONG_DESC) + host.plugins["TICKETS_IMPORT"].register( + "bugzilla", self.Import, SHORT_DESC, LONG_DESC + ) def Import(self, client, location, options=None): if not os.path.isabs(location): - raise exceptions.DataError(u"An absolute path to XML data need to be given as location") + raise exceptions.DataError( + u"An absolute path to XML data need to be given as location" + ) bugzilla_parser = BugzillaParser() # d = threads.deferToThread(bugzilla_parser.parse, location) d = defer.maybeDeferred(bugzilla_parser.parse, location) diff --git a/sat/plugins/plugin_tmp_directory_subscription.py b/sat/plugins/plugin_tmp_directory_subscription.py index 5cc6fae2..24fb779e 100644 --- a/sat/plugins/plugin_tmp_directory_subscription.py +++ b/sat/plugins/plugin_tmp_directory_subscription.py @@ -21,6 +21,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) @@ -33,7 +34,7 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "DirectorySubscription", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of directory subscription""") + C.PI_DESCRIPTION: _("""Implementation of directory subscription"""), } @@ -42,11 +43,15 @@ CMD_UPDATE_SUBSCRIBTION = "update" class DirectorySubscription(object): - def __init__(self, host): log.info(_("Directory subscription plugin initialization")) self.host = host - host.importMenu((D_("Service"), D_("Directory subscription")), self.subscribe, security_limit=1, help_string=D_("User directory subscription")) + host.importMenu( + (D_("Service"), D_("Directory subscription")), + self.subscribe, + security_limit=1, + help_string=D_("User directory subscription"), + ) def subscribe(self, raw_data, profile): """Request available commands on the jabber search service associated to profile's host. @@ -59,7 +64,9 @@ class DirectorySubscription(object): def got_services(services): service_jid = services[0] - session_id, session_data = self.host.plugins["XEP-0050"].requesting.newSession(profile=profile) + session_id, session_data = self.host.plugins[ + "XEP-0050" + ].requesting.newSession(profile=profile) session_data["jid"] = service_jid session_data["node"] = CMD_UPDATE_SUBSCRIBTION data = {"session_id": session_id} diff --git a/sat/plugins/plugin_xep_0020.py b/sat/plugins/plugin_xep_0020.py index 16c0af90..30c5a3dd 100755 --- a/sat/plugins/plugin_xep_0020.py +++ b/sat/plugins/plugin_xep_0020.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.xish import domish @@ -33,7 +34,7 @@ except ImportError: from wokkel import disco, iwokkel, data_form -NS_FEATURE_NEG = 'http://jabber.org/protocol/feature-neg' +NS_FEATURE_NEG = "http://jabber.org/protocol/feature-neg" PLUGIN_INFO = { C.PI_NAME: "XEP 0020 Plugin", @@ -42,12 +43,11 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0020"], C.PI_MAIN: "XEP_0020", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation""") + C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation"""), } class XEP_0020(object): - def __init__(self, host): log.info(_("Plugin XEP_0020 initialization")) @@ -62,7 +62,7 @@ class XEP_0020(object): @raise exceptions.NotFound: no feature element found """ try: - feature_elt = elt.elements(NS_FEATURE_NEG, 'feature').next() + feature_elt = elt.elements(NS_FEATURE_NEG, "feature").next() except StopIteration: raise exceptions.NotFound return feature_elt @@ -100,7 +100,11 @@ class XEP_0020(object): values = form.fields[field].values result[field] = values[0] if values else None if len(values) > 1: - log.warning(_(u"More than one value choosed for {}, keeping the first one").format(field)) + log.warning( + _( + u"More than one value choosed for {}, keeping the first one" + ).format(field) + ) return result def negotiate(self, feature_elt, name, negotiable_values, namespace): @@ -126,8 +130,8 @@ class XEP_0020(object): @param options(dict): dict with feature as key and choosed option as value @param namespace (None, unicode): form namespace or None to ignore """ - feature_elt = domish.Element((NS_FEATURE_NEG, 'feature')) - x_form = data_form.Form('submit', formNamespace=namespace) + feature_elt = domish.Element((NS_FEATURE_NEG, "feature")) + x_form = data_form.Form("submit", formNamespace=namespace) x_form.makeFields(options) feature_elt.addChild(x_form.toElement()) return feature_elt @@ -138,11 +142,16 @@ class XEP_0020(object): @param options_dict(dict): dict with feature as key and iterable of acceptable options as value @param namespace(None, unicode): feature namespace """ - feature_elt = domish.Element((NS_FEATURE_NEG, 'feature')) - x_form = data_form.Form('form', formNamespace=namespace) + feature_elt = domish.Element((NS_FEATURE_NEG, "feature")) + x_form = data_form.Form("form", formNamespace=namespace) for field in options_dict: - x_form.addField(data_form.Field('list-single', field, - options=[data_form.Option(option) for option in options_dict[field]])) + x_form.addField( + data_form.Field( + "list-single", + field, + options=[data_form.Option(option) for option in options_dict[field]], + ) + ) feature_elt.addChild(x_form.toElement()) return feature_elt @@ -150,8 +159,8 @@ class XEP_0020(object): class XEP_0020_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_FEATURE_NEG)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0033.py b/sat/plugins/plugin_xep_0033.py index dd92faa0..bb9a1fca 100644 --- a/sat/plugins/plugin_xep_0033.py +++ b/sat/plugins/plugin_xep_0033.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -27,6 +28,7 @@ from zope.interface import implements from twisted.words.protocols.jabber.jid import JID from twisted.python import failure import copy + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -61,7 +63,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0033", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing""") + C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing"""), } @@ -69,38 +71,65 @@ class XEP_0033(object): """ Implementation for XEP 0033 """ + def __init__(self, host): log.info(_("Extended Stanza Addressing plugin initialization")) self.host = host self.internal_data = {} - host.trigger.add("sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY) + host.trigger.add( + "sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY + ) host.trigger.add("MessageReceived", self.messageReceivedTrigger) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Process the XEP-0033 related data to be sent""" profile = client.profile def treatment(mess_data): - if not 'address' in mess_data['extra']: + if not "address" in mess_data["extra"]: return mess_data def discoCallback(entities): if not entities: - log.warning(_("XEP-0033 is being used but the server doesn't support it!")) - raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033')) + log.warning( + _("XEP-0033 is being used but the server doesn't support it!") + ) + raise failure.Failure( + exceptions.CancelError(u"Cancelled by XEP-0033") + ) if mess_data["to"] not in entities: - expected = _(' or ').join([entity.userhost() for entity in entities]) - log.warning(_(u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!") % {'expected': expected, 'current': mess_data["to"]}) - log.warning(_(u"TODO: addressing has been fixed by the backend... fix it in the frontend!")) + expected = _(" or ").join([entity.userhost() for entity in entities]) + log.warning( + _( + u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!" + ) + % {"expected": expected, "current": mess_data["to"]} + ) + log.warning( + _( + u"TODO: addressing has been fixed by the backend... fix it in the frontend!" + ) + ) mess_data["to"] = list(entities)[0].userhostJID() - element = mess_data['xml'].addElement('addresses', NS_ADDRESS) - entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != ''] + element = mess_data["xml"].addElement("addresses", NS_ADDRESS) + entries = [ + entry.split(":") + for entry in mess_data["extra"]["address"].split("\n") + if entry != "" + ] for type_, jid_ in entries: - element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_})) + element.addChild( + domish.Element( + (None, "address"), None, {"type": type_, "jid": jid_} + ) + ) # when the prosody plugin is completed, we can immediately return mess_data from here self.sendAndStoreMessage(mess_data, entries, profile) log.debug("XEP-0033 took over") - raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033')) + raise failure.Failure(exceptions.CancelError(u"Cancelled by XEP-0033")) + d = self.host.findFeaturesSet(client, [NS_ADDRESS]) d.addCallbacks(discoCallback, lambda dummy: discoCallback(None)) return d @@ -122,6 +151,7 @@ class XEP_0033(object): - change the messageNew signal to eventually pass more than one recipient """ client = self.host.getClient(profile) + def send(mess_data, skip_send=False): d = defer.Deferred() if not skip_send: @@ -133,13 +163,13 @@ class XEP_0033(object): def discoCallback(entities, to_jid_s): history_data = copy.deepcopy(mess_data) - history_data['to'] = JID(to_jid_s) - history_data['xml']['to'] = to_jid_s + history_data["to"] = JID(to_jid_s) + history_data["xml"]["to"] = to_jid_s if entities: if entities not in self.internal_data[timestamp]: sent_data = copy.deepcopy(mess_data) - sent_data['to'] = JID(JID(to_jid_s).host) - sent_data['xml']['to'] = JID(to_jid_s).host + sent_data["to"] = JID(JID(to_jid_s).host) + sent_data["xml"]["to"] = JID(to_jid_s).host send(sent_data) self.internal_data[timestamp].append(entities) # we still need to fill the history and signal the echo... @@ -156,8 +186,12 @@ class XEP_0033(object): defer_list = [] for type_, jid_ in entries: d = defer.Deferred() - d.addCallback(self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host)) - d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_]) + d.addCallback( + self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host) + ) + d.addCallbacks( + discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_] + ) d.callback([NS_ADDRESS]) defer_list.append(d) d = defer.Deferred().addCallback(lambda dummy: self.internal_data.pop(timestamp)) @@ -165,17 +199,21 @@ class XEP_0033(object): def messageReceivedTrigger(self, client, message, post_treat): """In order to save the addressing information in the history""" + def post_treat_addr(data, addresses): - data['extra']['addresses'] = "" + data["extra"]["addresses"] = "" for address in addresses: # Depending how message has been constructed, we could get here # some noise like "\n " instead of an address element. if isinstance(address, domish.Element): - data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid']) + data["extra"]["addresses"] += "%s:%s\n" % ( + address["type"], + address["jid"], + ) return data try: - addresses = message.elements(NS_ADDRESS, 'addresses').next() + addresses = message.elements(NS_ADDRESS, "addresses").next() except StopIteration: pass # no addresses else: @@ -194,8 +232,8 @@ class XEP_0033_handler(XMPPHandler): self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_ADDRESS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0047.py b/sat/plugins/plugin_xep_0047.py index bd8bf7a6..f40ab364 100644 --- a/sat/plugins/plugin_xep_0047.py +++ b/sat/plugins/plugin_xep_0047.py @@ -19,6 +19,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.core import exceptions @@ -40,15 +41,15 @@ try: except ImportError: from wokkel.subprotocols import XMPPHandler -MESSAGE = '/message' +MESSAGE = "/message" IQ_SET = '/iq[@type="set"]' -NS_IBB = 'http://jabber.org/protocol/ibb' +NS_IBB = "http://jabber.org/protocol/ibb" IBB_OPEN = IQ_SET + '/open[@xmlns="' + NS_IBB + '"]' IBB_CLOSE = IQ_SET + '/close[@xmlns="' + NS_IBB + '" and @sid="{}"]' IBB_IQ_DATA = IQ_SET + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]' IBB_MESSAGE_DATA = MESSAGE + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]' TIMEOUT = 120 # timeout for workflow -DEFER_KEY = 'finished' # key of the deferred used to track session end +DEFER_KEY = "finished" # key of the deferred used to track session end PLUGIN_INFO = { C.PI_NAME: "In-Band Bytestream Plugin", @@ -58,7 +59,7 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0047"], C.PI_MAIN: "XEP_0047", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams"""), } @@ -82,8 +83,11 @@ class XEP_0047(object): @param sid(unicode): session id of client.xep_0047_current_stream @param client: %(doc_client)s """ - log.info(u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]" - .format(sid=sid, profile=client.profile)) + log.info( + u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]".format( + sid=sid, profile=client.profile + ) + ) self._killSession(sid, client, "TIMEOUT") def _killSession(self, sid, client, failure_reason=None): @@ -101,14 +105,14 @@ class XEP_0047(object): return try: - observer_cb = session['observer_cb'] + observer_cb = session["observer_cb"] except KeyError: pass else: client.xmlstream.removeObserver(session["event_data"], observer_cb) - if session['timer'].active(): - session['timer'].cancel() + if session["timer"].active(): + session["timer"].cancel() del client.xep_0047_current_stream[sid] @@ -136,15 +140,15 @@ class XEP_0047(object): @return (dict): session data """ if sid in client.xep_0047_current_stream: - raise exceptions.ConflictError(u'A session with this id already exists !') - session_data = client.xep_0047_current_stream[sid] = \ - {'id': sid, - DEFER_KEY: defer.Deferred(), - 'to': to_jid, - 'stream_object': stream_object, - 'seq': -1, - 'timer': reactor.callLater(TIMEOUT, self._timeOut, sid, client), - } + raise exceptions.ConflictError(u"A session with this id already exists !") + session_data = client.xep_0047_current_stream[sid] = { + "id": sid, + DEFER_KEY: defer.Deferred(), + "to": to_jid, + "stream_object": stream_object, + "seq": -1, + "timer": reactor.callLater(TIMEOUT, self._timeOut, sid, client), + } return session_data @@ -155,19 +159,21 @@ class XEP_0047(object): """ log.debug(_(u"IBB stream opening")) iq_elt.handled = True - open_elt = iq_elt.elements(NS_IBB, 'open').next() - block_size = open_elt.getAttribute('block-size') - sid = open_elt.getAttribute('sid') - stanza = open_elt.getAttribute('stanza', 'iq') + open_elt = iq_elt.elements(NS_IBB, "open").next() + block_size = open_elt.getAttribute("block-size") + sid = open_elt.getAttribute("sid") + stanza = open_elt.getAttribute("stanza", "iq") if not sid or not block_size or int(block_size) > 65535: - return self._sendError('not-acceptable', sid or None, iq_elt, client) + return self._sendError("not-acceptable", sid or None, iq_elt, client) if not sid in client.xep_0047_current_stream: log.warning(_(u"Ignoring unexpected IBB transfer: %s" % sid)) - return self._sendError('not-acceptable', sid or None, iq_elt, client) + return self._sendError("not-acceptable", sid or None, iq_elt, client) session_data = client.xep_0047_current_stream[sid] - if session_data["to"] != jid.JID(iq_elt['from']): - log.warning(_("sended jid inconsistency (man in the middle attack attempt ?)")) - return self._sendError('not-acceptable', sid, iq_elt, client) + if session_data["to"] != jid.JID(iq_elt["from"]): + log.warning( + _("sended jid inconsistency (man in the middle attack attempt ?)") + ) + return self._sendError("not-acceptable", sid, iq_elt, client) # at this stage, the session looks ok and will be accepted @@ -175,7 +181,9 @@ class XEP_0047(object): session_data["timer"].reset(TIMEOUT) # we save the xmlstream, events and observer data to allow observer removal - session_data["event_data"] = event_data = (IBB_MESSAGE_DATA if stanza == 'message' else IBB_IQ_DATA).format(sid) + session_data["event_data"] = event_data = ( + IBB_MESSAGE_DATA if stanza == "message" else IBB_IQ_DATA + ).format(sid) session_data["observer_cb"] = observer_cb = self._onIBBData event_close = IBB_CLOSE.format(sid) # we now set the stream observer to look after data packet @@ -184,7 +192,7 @@ class XEP_0047(object): client.xmlstream.addObserver(event_data, observer_cb, client=client) client.xmlstream.addOnetimeObserver(event_close, self._onIBBClose, client=client) # finally, we send the accept stanza - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') + iq_result_elt = xmlstream.toResponse(iq_elt, "result") client.send(iq_result_elt) def _onIBBClose(self, iq_elt, client): @@ -194,11 +202,11 @@ class XEP_0047(object): """ iq_elt.handled = True log.debug(_("IBB stream closing")) - close_elt = iq_elt.elements(NS_IBB, 'close').next() + close_elt = iq_elt.elements(NS_IBB, "close").next() # XXX: this observer is only triggered on valid sid, so we don't need to check it - sid = close_elt['sid'] + sid = close_elt["sid"] - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') + iq_result_elt = xmlstream.toResponse(iq_elt, "result") client.send(iq_result_elt) self._killSession(sid, client) @@ -209,29 +217,33 @@ class XEP_0047(object): @param element(domish.Element):or stanza """ element.handled = True - data_elt = element.elements(NS_IBB, 'data').next() - sid = data_elt['sid'] + data_elt = element.elements(NS_IBB, "data").next() + sid = data_elt["sid"] try: session_data = client.xep_0047_current_stream[sid] except KeyError: log.warning(_(u"Received data for an unknown session id")) - return self._sendError('item-not-found', None, element, client) + return self._sendError("item-not-found", None, element, client) from_jid = session_data["to"] stream_object = session_data["stream_object"] - if from_jid.full() != element['from']: - log.warning(_(u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}").format(initial=from_jid, given=element['from'])) - if element.name == 'iq': - self._sendError('not-acceptable', sid, element, client) + if from_jid.full() != element["from"]: + log.warning( + _( + u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}" + ).format(initial=from_jid, given=element["from"]) + ) + if element.name == "iq": + self._sendError("not-acceptable", sid, element, client) return session_data["seq"] = (session_data["seq"] + 1) % 65535 if int(data_elt.getAttribute("seq", -1)) != session_data["seq"]: log.warning(_(u"Sequence error")) - if element.name == 'iq': - reason = 'not-acceptable' + if element.name == "iq": + reason = "not-acceptable" self._sendError(reason, sid, element, client) self.terminateStream(session_data, client, reason) return @@ -245,14 +257,14 @@ class XEP_0047(object): except TypeError: # The base64 data is invalid log.warning(_(u"Invalid base64 data")) - if element.name == 'iq': - self._sendError('not-acceptable', sid, element, client) + if element.name == "iq": + self._sendError("not-acceptable", sid, element, client) self.terminateStream(session_data, client, reason) return # we can now ack success - if element.name == 'iq': - iq_result_elt = xmlstream.toResponse(element, 'result') + if element.name == "iq": + iq_result_elt = xmlstream.toResponse(element, "result") client.send(iq_result_elt) def _sendError(self, error_condition, sid, iq_elt, client): @@ -264,7 +276,11 @@ class XEP_0047(object): @param client: %(doc_client)s """ iq_elt = error.StanzaError(error_condition).toResponse(iq_elt) - log.warning(u"Error while managing in-band bytestream session, cancelling: {}".format(error_condition)) + log.warning( + u"Error while managing in-band bytestream session, cancelling: {}".format( + error_condition + ) + ) if sid is not None: self._killSession(sid, client, error_condition) client.send(iq_elt) @@ -285,11 +301,11 @@ class XEP_0047(object): session_data["block_size"] = block_size iq_elt = client.IQ() - iq_elt['to'] = to_jid.full() - open_elt = iq_elt.addElement((NS_IBB, 'open')) - open_elt['block-size'] = str(block_size) - open_elt['sid'] = sid - open_elt['stanza'] = 'iq' # TODO: manage stanza ? + iq_elt["to"] = to_jid.full() + open_elt = iq_elt.addElement((NS_IBB, "open")) + open_elt["block-size"] = str(block_size) + open_elt["sid"] = sid + open_elt["stanza"] = "iq" # TODO: manage stanza ? args = [session_data, client] d = iq_elt.send() d.addCallbacks(self._IQDataStreamCb, self._IQDataStreamEb, args, None, args) @@ -307,11 +323,11 @@ class XEP_0047(object): buffer_ = session_data["stream_object"].read(session_data["block_size"]) if buffer_: next_iq_elt = client.IQ() - next_iq_elt['to'] = session_data["to"].full() - data_elt = next_iq_elt.addElement((NS_IBB, 'data')) - seq = session_data['seq'] = (session_data['seq'] + 1) % 65535 - data_elt['seq'] = unicode(seq) - data_elt['sid'] = session_data['id'] + next_iq_elt["to"] = session_data["to"].full() + data_elt = next_iq_elt.addElement((NS_IBB, "data")) + seq = session_data["seq"] = (session_data["seq"] + 1) % 65535 + data_elt["seq"] = unicode(seq) + data_elt["sid"] = session_data["id"] data_elt.addContent(base64.b64encode(buffer_)) args = [session_data, client] d = next_iq_elt.send() @@ -334,11 +350,11 @@ class XEP_0047(object): @param failure_reason(unicode, None): reason of the failure, or None if steam was successful """ iq_elt = client.IQ() - iq_elt['to'] = session_data["to"].full() - close_elt = iq_elt.addElement((NS_IBB, 'close')) - close_elt['sid'] = session_data['id'] + iq_elt["to"] = session_data["to"].full() + close_elt = iq_elt.addElement((NS_IBB, "close")) + close_elt["sid"] = session_data["id"] iq_elt.send() - self._killSession(session_data['id'], client, failure_reason) + self._killSession(session_data["id"], client, failure_reason) class XEP_0047_handler(XMPPHandler): @@ -348,10 +364,12 @@ class XEP_0047_handler(XMPPHandler): self.plugin_parent = parent def connectionInitialized(self): - self.xmlstream.addObserver(IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent) + self.xmlstream.addObserver( + IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_IBB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0048.py b/sat/plugins/plugin_xep_0048.py index 8b71f0a6..e6230ba2 100644 --- a/sat/plugins/plugin_xep_0048.py +++ b/sat/plugins/plugin_xep_0048.py @@ -23,6 +23,7 @@ from sat.core.constants import Const as C from sat.memory.persistent import PersistentBinaryDict from sat.tools import xml_tools from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import jid @@ -30,7 +31,7 @@ from twisted.words.protocols.jabber.error import StanzaError from twisted.internet import defer -NS_BOOKMARKS = 'storage:bookmarks' +NS_BOOKMARKS = "storage:bookmarks" PLUGIN_INFO = { C.PI_NAME: "Bookmarks", @@ -41,28 +42,56 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["XEP-0049"], C.PI_MAIN: "XEP_0048", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of bookmarks""") + C.PI_DESCRIPTION: _("""Implementation of bookmarks"""), } class XEP_0048(object): - MUC_TYPE = 'muc' - URL_TYPE = 'url' - MUC_KEY = 'jid' - URL_KEY = 'url' - MUC_ATTRS = ('autojoin', 'name') - URL_ATTRS = ('name',) + MUC_TYPE = "muc" + URL_TYPE = "url" + MUC_KEY = "jid" + URL_KEY = "url" + MUC_ATTRS = ("autojoin", "name") + URL_ATTRS = ("name",) def __init__(self, host): log.info(_("Bookmarks plugin initialization")) self.host = host # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True) self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True) - host.importMenu((D_("Groups"), D_("Bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks")) - self.__selected_id = host.registerCallback(self._bookmarkSelectedCb, with_data=True) - host.bridge.addMethod("bookmarksList", ".plugin", in_sign='sss', out_sign='a{sa{sa{ss}}}', method=self._bookmarksList, async=True) - host.bridge.addMethod("bookmarksRemove", ".plugin", in_sign='ssss', out_sign='', method=self._bookmarksRemove, async=True) - host.bridge.addMethod("bookmarksAdd", ".plugin", in_sign='ssa{ss}ss', out_sign='', method=self._bookmarksAdd, async=True) + host.importMenu( + (D_("Groups"), D_("Bookmarks")), + self._bookmarksMenu, + security_limit=0, + help_string=D_("Use and manage bookmarks"), + ) + self.__selected_id = host.registerCallback( + self._bookmarkSelectedCb, with_data=True + ) + host.bridge.addMethod( + "bookmarksList", + ".plugin", + in_sign="sss", + out_sign="a{sa{sa{ss}}}", + method=self._bookmarksList, + async=True, + ) + host.bridge.addMethod( + "bookmarksRemove", + ".plugin", + in_sign="ssss", + out_sign="", + method=self._bookmarksRemove, + async=True, + ) + host.bridge.addMethod( + "bookmarksAdd", + ".plugin", + in_sign="ssa{ss}ss", + out_sign="", + method=self._bookmarksAdd, + async=True, + ) try: self.private_plg = self.host.plugins["XEP-0049"] except KeyError: @@ -74,20 +103,22 @@ class XEP_0048(object): @defer.inlineCallbacks def profileConnected(self, client): - local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, client.profile) + local = client.bookmarks_local = PersistentBinaryDict( + NS_BOOKMARKS, client.profile + ) yield local.load() if not local: local[XEP_0048.MUC_TYPE] = dict() local[XEP_0048.URL_TYPE] = dict() - private = yield self._getServerBookmarks('private', client.profile) + private = yield self._getServerBookmarks("private", client.profile) pubsub = client.bookmarks_pubsub = None for bookmarks in (local, private, pubsub): if bookmarks is not None: for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items(): - if data.get('autojoin', 'false') == 'true': - nick = data.get('nick', client.jid.user) - self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) + if data.get("autojoin", "false") == "true": + nick = data.get("nick", client.jid.user) + self.host.plugins["XEP-0045"].join(client, room_jid, nick, {}) @defer.inlineCallbacks def _getServerBookmarks(self, storage_type, profile): @@ -101,14 +132,18 @@ class XEP_0048(object): @return: data dictionary, or None if feature is not available """ client = self.host.getClient(profile) - if storage_type == 'private': + if storage_type == "private": try: - bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile) - data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml) + bookmarks_private_xml = yield self.private_plg.privateXMLGet( + "storage", NS_BOOKMARKS, profile + ) + data = client.bookmarks_private = self._bookmarkElt2Dict( + bookmarks_private_xml + ) except (StanzaError, AttributeError): log.info(_("Private XML storage not available")) data = client.bookmarks_private = None - elif storage_type == 'pubsub': + elif storage_type == "pubsub": raise NotImplementedError else: raise ValueError("storage_type must be 'private' or 'pubsub'") @@ -124,9 +159,9 @@ class XEP_0048(object): @param bookmarks_elt (domish.Element): bookmarks XML @param profile: %(doc_profile)s """ - if storage_type == 'private': + if storage_type == "private": yield self.private_plg.privateXMLStore(bookmarks_elt, profile) - elif storage_type == 'pubsub': + elif storage_type == "pubsub": raise NotImplementedError else: raise ValueError("storage_type must be 'private' or 'pubsub'") @@ -142,12 +177,14 @@ class XEP_0048(object): conf_data = {} url_data = {} - conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference') + conference_elts = storage_elt.elements(NS_BOOKMARKS, "conference") for conference_elt in conference_elts: try: room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY]) except KeyError: - log.warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml()) + log.warning( + "invalid bookmark found, igoring it:\n%s" % conference_elt.toXml() + ) continue data = conf_data[room_jid] = {} @@ -156,17 +193,19 @@ class XEP_0048(object): if conference_elt.hasAttribute(attr): data[attr] = conference_elt[attr] try: - data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next()) + data["nick"] = unicode( + conference_elt.elements(NS_BOOKMARKS, "nick").next() + ) except StopIteration: pass # TODO: manage password (need to be secured, see XEP-0049 §4) - url_elts = storage_elt.elements(NS_BOOKMARKS, 'url') + url_elts = storage_elt.elements(NS_BOOKMARKS, "url") for url_elt in url_elts: try: url = url_elt[XEP_0048.URL_KEY] except KeyError: - log.warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml()) + log.warning("invalid bookmark found, igoring it:\n%s" % url_elt.toXml()) continue data = url_data[url] = {} for attr in XEP_0048.URL_ATTRS: @@ -185,9 +224,9 @@ class XEP_0048(object): """ rooms_data = data.get(XEP_0048.MUC_TYPE, {}) urls_data = data.get(XEP_0048.URL_TYPE, {}) - storage_elt = domish.Element((NS_BOOKMARKS, 'storage')) + storage_elt = domish.Element((NS_BOOKMARKS, "storage")) for room_jid in rooms_data: - conference_elt = storage_elt.addElement('conference') + conference_elt = storage_elt.addElement("conference") conference_elt[XEP_0048.MUC_KEY] = room_jid.full() for attr in XEP_0048.MUC_ATTRS: try: @@ -195,12 +234,12 @@ class XEP_0048(object): except KeyError: pass try: - conference_elt.addElement('nick', content=rooms_data[room_jid]['nick']) + conference_elt.addElement("nick", content=rooms_data[room_jid]["nick"]) except KeyError: pass for url in urls_data: - url_elt = storage_elt.addElement('url') + url_elt = storage_elt.addElement("url") url_elt[XEP_0048.URL_KEY] = url for attr in XEP_0048.URL_ATTRS: try: @@ -212,18 +251,20 @@ class XEP_0048(object): def _bookmarkSelectedCb(self, data, profile): try: - room_jid_s, nick = data['index'].split(' ', 1) + room_jid_s, nick = data["index"].split(" ", 1) room_jid = jid.JID(room_jid_s) except (KeyError, RuntimeError): log.warning(_("No room jid selected")) return {} client = self.host.getClient(profile) - d = self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) + d = self.host.plugins["XEP-0045"].join(client, room_jid, nick, {}) + def join_eb(failure): log.warning(u"Error while trying to join room: {}".format(failure)) # FIXME: failure are badly managed in plugin XEP-0045. Plugin XEP-0045 need to be fixed before managing errors correctly here return {} + d.addCallbacks(lambda dummy: {}, join_eb) return d @@ -233,40 +274,54 @@ class XEP_0048(object): """ client = self.host.getClient(profile) - xmlui = xml_tools.XMLUI(title=_('Bookmarks manager')) - adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id) - for bookmarks in (client.bookmarks_local, client.bookmarks_private, client.bookmarks_pubsub): + xmlui = xml_tools.XMLUI(title=_("Bookmarks manager")) + adv_list = xmlui.changeContainer( + "advanced_list", + columns=3, + selectable="single", + callback_id=self.__selected_id, + ) + for bookmarks in ( + client.bookmarks_local, + client.bookmarks_private, + client.bookmarks_pubsub, + ): if bookmarks is None: continue - for (room_jid, data) in sorted(bookmarks[XEP_0048.MUC_TYPE].items(), key=lambda item: item[1].get('name',item[0].user)): + for (room_jid, data) in sorted( + bookmarks[XEP_0048.MUC_TYPE].items(), + key=lambda item: item[1].get("name", item[0].user), + ): room_jid_s = room_jid.full() - adv_list.setRowIndex(u'%s %s' % (room_jid_s, data.get('nick') or client.jid.user)) - xmlui.addText(data.get('name','')) + adv_list.setRowIndex( + u"%s %s" % (room_jid_s, data.get("nick") or client.jid.user) + ) + xmlui.addText(data.get("name", "")) xmlui.addJid(room_jid) - if data.get('autojoin', 'false') == 'true': - xmlui.addText('autojoin') + if data.get("autojoin", "false") == "true": + xmlui.addText("autojoin") else: xmlui.addEmpty() adv_list.end() - xmlui.addDivider('dash') + xmlui.addDivider("dash") xmlui.addText(_("add a bookmark")) xmlui.changeContainer("pairs") - xmlui.addLabel(_('Name')) - xmlui.addString('name') - xmlui.addLabel(_('jid')) - xmlui.addString('jid') - xmlui.addLabel(_('Nickname')) - xmlui.addString('nick', client.jid.user) - xmlui.addLabel(_('Autojoin')) - xmlui.addBool('autojoin') + xmlui.addLabel(_("Name")) + xmlui.addString("name") + xmlui.addLabel(_("jid")) + xmlui.addString("jid") + xmlui.addLabel(_("Nickname")) + xmlui.addString("nick", client.jid.user) + xmlui.addLabel(_("Autojoin")) + xmlui.addBool("autojoin") xmlui.changeContainer("vertical") - xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin')) - return {'xmlui': xmlui.toXml()} + xmlui.addButton(self.__bm_save_id, _("Save"), ("name", "jid", "nick", "autojoin")) + return {"xmlui": xmlui.toXml()} def _bookmarksSaveCb(self, data, profile): bm_data = xml_tools.XMLUIResult2DataFormResult(data) try: - location = jid.JID(bm_data.pop('jid')) + location = jid.JID(bm_data.pop("jid")) except KeyError: raise exceptions.InternalError("Can't find mandatory key") d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile) @@ -274,7 +329,9 @@ class XEP_0048(object): return d @defer.inlineCallbacks - def addBookmark(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): + def addBookmark( + self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE + ): """Store a new bookmark @param type_: bookmark type, one of: @@ -293,21 +350,21 @@ class XEP_0048(object): - "local": Store in SàT database @param profile_key: %(doc_profile_key)s """ - assert storage_type in ('auto', 'pubsub', 'private', 'local') - if type_ == XEP_0048.URL_TYPE and {'autojoin', 'nick'}.intersection(data.keys()): - raise ValueError("autojoin or nick can't be used with URLs") + assert storage_type in ("auto", "pubsub", "private", "local") + if type_ == XEP_0048.URL_TYPE and {"autojoin", "nick"}.intersection(data.keys()): + raise ValueError("autojoin or nick can't be used with URLs") client = self.host.getClient(profile_key) - if storage_type == 'auto': + if storage_type == "auto": if client.bookmarks_pubsub is not None: - storage_type = 'pubsub' + storage_type = "pubsub" elif client.bookmarks_private is not None: - storage_type = 'private' + storage_type = "private" else: - storage_type = 'local' + storage_type = "local" log.warning(_("Bookmarks will be local only")) log.info(_('Type selected for "auto" storage: %s') % storage_type) - if storage_type == 'local': + if storage_type == "local": client.bookmarks_local[type_][location] = data yield client.bookmarks_local.force(type_) else: @@ -317,7 +374,9 @@ class XEP_0048(object): yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile) @defer.inlineCallbacks - def removeBookmark(self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE): + def removeBookmark( + self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE + ): """Remove a stored bookmark @param type_: bookmark type, one of: @@ -331,26 +390,26 @@ class XEP_0048(object): - "local": Store in SàT database @param profile_key: %(doc_profile_key)s """ - assert storage_type in ('all', 'pubsub', 'private', 'local') + assert storage_type in ("all", "pubsub", "private", "local") client = self.host.getClient(profile_key) - if storage_type in ('all', 'local'): + if storage_type in ("all", "local"): try: del client.bookmarks_local[type_][location] yield client.bookmarks_local.force(type_) except KeyError: log.debug("Bookmark is not present in local storage") - if storage_type in ('all', 'private'): - bookmarks = yield self._getServerBookmarks('private', client.profile) + if storage_type in ("all", "private"): + bookmarks = yield self._getServerBookmarks("private", client.profile) try: del bookmarks[type_][location] bookmark_elt = self._dict2BookmarkElt(type_, bookmarks) - yield self._setServerBookmarks('private', bookmark_elt, client.profile) + yield self._setServerBookmarks("private", bookmark_elt, client.profile) except KeyError: log.debug("Bookmark is not present in private storage") - if storage_type == 'pubsub': + if storage_type == "pubsub": raise NotImplementedError def _bookmarksList(self, type_, storage_location, profile_key=C.PROF_KEY_NONE): @@ -381,10 +440,10 @@ class XEP_0048(object): ret[_storage_location][bookmark.full()] = data[bookmark].copy() return ret - for _storage_location in ('local', 'private', 'pubsub'): - if storage_location in ('all', _storage_location): + for _storage_location in ("local", "private", "pubsub"): + if storage_location in ("all", _storage_location): ret[_storage_location] = {} - if _storage_location in ('private',): + if _storage_location in ("private",): # we update distant bookmarks, just in case an other client added something d = self._getServerBookmarks(_storage_location, client.profile) else: @@ -394,7 +453,9 @@ class XEP_0048(object): return ret_d - def _bookmarksRemove(self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE): + def _bookmarksRemove( + self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE + ): """Return stored bookmarks @param type_: bookmark type, one of: @@ -412,7 +473,9 @@ class XEP_0048(object): location = jid.JID(location) return self.removeBookmark(type_, location, storage_location, profile_key) - def _bookmarksAdd(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): + def _bookmarksAdd( + self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE + ): if type_ == XEP_0048.MUC_TYPE: location = jid.JID(location) return self.addBookmark(type_, location, data, storage_type, profile_key) @@ -427,21 +490,26 @@ class XEP_0048(object): txt_cmd = self.host.plugins[C.TEXT_CMDS] options = mess_data["unparsed"].strip().split() - if options and options[0] not in ('autojoin', 'remove'): + if options and options[0] not in ("autojoin", "remove"): txt_cmd.feedBack(client, _("Bad arguments"), mess_data) return False room_jid = mess_data["to"].userhostJID() if "remove" in options: - self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key = client.profile) - txt_cmd.feedBack(client, _("All [%s] bookmarks are being removed") % room_jid.full(), mess_data) + self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key=client.profile) + txt_cmd.feedBack( + client, + _("All [%s] bookmarks are being removed") % room_jid.full(), + mess_data, + ) return False - data = { "name": room_jid.user, - "nick": client.jid.user, - "autojoin": "true" if "autojoin" in options else "false", - } + data = { + "name": room_jid.user, + "nick": client.jid.user, + "autojoin": "true" if "autojoin" in options else "false", + } self.addBookmark(XEP_0048.MUC_TYPE, room_jid, data, profile_key=client.profile) txt_cmd.feedBack(client, _("Bookmark added"), mess_data) diff --git a/sat/plugins/plugin_xep_0049.py b/sat/plugins/plugin_xep_0049.py index f098bb4d..d86b96a9 100755 --- a/sat/plugins/plugin_xep_0049.py +++ b/sat/plugins/plugin_xep_0049.py @@ -20,12 +20,12 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import compat from twisted.words.xish import domish - PLUGIN_INFO = { C.PI_NAME: "XEP-0049 Plugin", C.PI_IMPORT_NAME: "XEP-0049", @@ -34,12 +34,12 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0049", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of private XML storage""") + C.PI_DESCRIPTION: _("""Implementation of private XML storage"""), } class XEP_0049(object): - NS_PRIVATE = 'jabber:iq:private' + NS_PRIVATE = "jabber:iq:private" def __init__(self, host): log.info(_("Plugin XEP-0049 initialization")) @@ -55,7 +55,7 @@ class XEP_0049(object): client = self.host.getClient(profile_key) # XXX: feature announcement in disco#info is not mandatory in XEP-0049, so we have to try to use private XML, and react according to the answer iq_elt = compat.IQ(client.xmlstream) - query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE) + query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE) query_elt.addChild(element) return iq_elt.send() @@ -69,13 +69,14 @@ class XEP_0049(object): """ client = self.host.getClient(profile_key) # XXX: see privateXMLStore note about feature checking - iq_elt = compat.IQ(client.xmlstream, 'get') - query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE) + iq_elt = compat.IQ(client.xmlstream, "get") + query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE) query_elt.addElement(node_name, namespace) + def getCb(answer_iq_elt): - answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, 'query').next() + answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, "query").next() return answer_query_elt.firstChildElement() + d = iq_elt.send() d.addCallback(getCb) return d - diff --git a/sat/plugins/plugin_xep_0050.py b/sat/plugins/plugin_xep_0050.py index 71ab0536..c36bd835 100644 --- a/sat/plugins/plugin_xep_0050.py +++ b/sat/plugins/plugin_xep_0050.py @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.protocols import jabber @@ -41,7 +42,7 @@ except ImportError: from collections import namedtuple try: - from collections import OrderedDict # only available from python 2.7 + from collections import OrderedDict # only available from python 2.7 except ImportError: from ordereddict import OrderedDict @@ -51,12 +52,16 @@ ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list") ID_CMD_NODE = disco.DiscoIdentity("automation", "command-node") CMD_REQUEST = IQ_SET + '/command[@xmlns="' + NS_COMMANDS + '"]' -SHOWS = OrderedDict([('default', _('Online')), - ('away', _('Away')), - ('chat', _('Free for chat')), - ('dnd', _('Do not disturb')), - ('xa', _('Left')), - ('disconnect', _('Disconnect'))]) +SHOWS = OrderedDict( + [ + ("default", _("Online")), + ("away", _("Away")), + ("chat", _("Free for chat")), + ("dnd", _("Do not disturb")), + ("xa", _("Left")), + ("disconnect", _("Disconnect")), + ] +) PLUGIN_INFO = { C.PI_NAME: "Ad-Hoc Commands", @@ -65,12 +70,11 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0050"], C.PI_MAIN: "XEP_0050", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands""") + C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands"""), } class AdHocError(Exception): - def __init__(self, error_const): """ Error to be used from callback @param error_const: one of XEP_0050.ERROR @@ -78,16 +82,37 @@ class AdHocError(Exception): assert error_const in XEP_0050.ERROR self.callback_error = error_const + class AdHocCommand(XMPPHandler): implements(iwokkel.IDisco) - def __init__(self, parent, callback, label, node, features, timeout, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client): + def __init__( + self, + parent, + callback, + label, + node, + features, + timeout, + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + client, + ): self.parent = parent self.callback = callback self.label = label self.node = node self.features = [disco.DiscoFeature(feature) for feature in features] - self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups + self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = ( + allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + ) self.client = client self.sessions = Sessions(timeout=timeout) @@ -95,7 +120,7 @@ class AdHocCommand(XMPPHandler): return self.label def isAuthorised(self, requestor): - if '@ALL@' in self.allowed_magics: + if "@ALL@" in self.allowed_magics: return True forbidden = set(self.forbidden_jids) for group in self.forbidden_groups: @@ -107,18 +132,25 @@ class AdHocCommand(XMPPHandler): try: allowed.update(self.client.roster.getJidsFromGroup(group)) except exceptions.UnknownGroupError: - log.warning(_(u"The groups [%(group)s] is unknown for profile [%(profile)s])" % {'group':group, 'profile':self.client.profile})) + log.warning( + _( + u"The groups [%(group)s] is unknown for profile [%(profile)s])" + % {"group": group, "profile": self.client.profile} + ) + ) if requestor.userhostJID() in allowed: return True return False - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - if nodeIdentifier != NS_COMMANDS: # FIXME: we should manage other disco nodes here + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + if ( + nodeIdentifier != NS_COMMANDS + ): # FIXME: we should manage other disco nodes here return [] # identities = [ID_CMD_LIST if self.node == NS_COMMANDS else ID_CMD_NODE] # FIXME return [disco.DiscoFeature(NS_COMMANDS)] + self.features - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def _sendAnswer(self, callback_data, session_id, request): @@ -134,30 +166,30 @@ class AdHocCommand(XMPPHandler): @return: deferred """ payload, status, actions, note = callback_data - assert(isinstance(payload, domish.Element) or payload is None) - assert(status in XEP_0050.STATUS) + assert isinstance(payload, domish.Element) or payload is None + assert status in XEP_0050.STATUS if not actions: actions = [XEP_0050.ACTION.EXECUTE] - result = domish.Element((None, 'iq')) - result['type'] = 'result' - result['id'] = request['id'] - result['to'] = request['from'] - command_elt = result.addElement('command', NS_COMMANDS) - command_elt['sessionid'] = session_id - command_elt['node'] = self.node - command_elt['status'] = status + result = domish.Element((None, "iq")) + result["type"] = "result" + result["id"] = request["id"] + result["to"] = request["from"] + command_elt = result.addElement("command", NS_COMMANDS) + command_elt["sessionid"] = session_id + command_elt["node"] = self.node + command_elt["status"] = status if status != XEP_0050.STATUS.CANCELED: if status != XEP_0050.STATUS.COMPLETED: - actions_elt = command_elt.addElement('actions') - actions_elt['execute'] = actions[0] + actions_elt = command_elt.addElement("actions") + actions_elt["execute"] = actions[0] for action in actions: actions_elt.addElement(action) if note is not None: note_type, note_mess = note - note_elt = command_elt.addElement('note', content=note_mess) - note_elt['type'] = note_type + note_elt = command_elt.addElement("note", content=note_mess) + note_elt["type"] = note_type if payload is not None: command_elt.addChild(payload) @@ -181,54 +213,116 @@ class AdHocCommand(XMPPHandler): def onRequest(self, command_elt, requestor, action, session_id): if not self.isAuthorised(requestor): - return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent) + return self._sendError( + XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent + ) if session_id: try: session_data = self.sessions[session_id] except KeyError: - return self._sendError(XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent) - if session_data['requestor'] != requestor: - return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent) + return self._sendError( + XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent + ) + if session_data["requestor"] != requestor: + return self._sendError( + XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent + ) else: session_id, session_data = self.sessions.newSession() - session_data['requestor'] = requestor + session_data["requestor"] = requestor if action == XEP_0050.ACTION.CANCEL: d = defer.succeed((None, XEP_0050.STATUS.CANCELED, None, None)) else: - d = defer.maybeDeferred(self.callback, command_elt, session_data, action, self.node, self.client.profile) + d = defer.maybeDeferred( + self.callback, + command_elt, + session_data, + action, + self.node, + self.client.profile, + ) d.addCallback(self._sendAnswer, session_id, command_elt.parent) - d.addErrback(lambda failure, request: self._sendError(failure.value.callback_error, session_id, request), command_elt.parent) + d.addErrback( + lambda failure, request: self._sendError( + failure.value.callback_error, session_id, request + ), + command_elt.parent, + ) class XEP_0050(object): - STATUS = namedtuple('Status', ('EXECUTING', 'COMPLETED', 'CANCELED'))('executing', 'completed', 'canceled') - ACTION = namedtuple('Action', ('EXECUTE', 'CANCEL', 'NEXT', 'PREV'))('execute', 'cancel', 'next', 'prev') - NOTE = namedtuple('Note', ('INFO','WARN','ERROR'))('info','warn','error') - ERROR = namedtuple('Error', ('MALFORMED_ACTION', 'BAD_ACTION', 'BAD_LOCALE', 'BAD_PAYLOAD', 'BAD_SESSIONID', 'SESSION_EXPIRED', - 'FORBIDDEN', 'ITEM_NOT_FOUND', 'FEATURE_NOT_IMPLEMENTED', 'INTERNAL'))(('bad-request', 'malformed-action'), - ('bad-request', 'bad-action'), ('bad-request', 'bad-locale'), ('bad-request','bad-payload'), - ('bad-request','bad-sessionid'), ('not-allowed','session-expired'), ('forbidden', None), - ('item-not-found', None), ('feature-not-implemented', None), ('internal-server-error', None)) # XEP-0050 §4.4 Table 5 + STATUS = namedtuple("Status", ("EXECUTING", "COMPLETED", "CANCELED"))( + "executing", "completed", "canceled" + ) + ACTION = namedtuple("Action", ("EXECUTE", "CANCEL", "NEXT", "PREV"))( + "execute", "cancel", "next", "prev" + ) + NOTE = namedtuple("Note", ("INFO", "WARN", "ERROR"))("info", "warn", "error") + ERROR = namedtuple( + "Error", + ( + "MALFORMED_ACTION", + "BAD_ACTION", + "BAD_LOCALE", + "BAD_PAYLOAD", + "BAD_SESSIONID", + "SESSION_EXPIRED", + "FORBIDDEN", + "ITEM_NOT_FOUND", + "FEATURE_NOT_IMPLEMENTED", + "INTERNAL", + ), + )( + ("bad-request", "malformed-action"), + ("bad-request", "bad-action"), + ("bad-request", "bad-locale"), + ("bad-request", "bad-payload"), + ("bad-request", "bad-sessionid"), + ("not-allowed", "session-expired"), + ("forbidden", None), + ("item-not-found", None), + ("feature-not-implemented", None), + ("internal-server-error", None), + ) # XEP-0050 §4.4 Table 5 def __init__(self, host): log.info(_("plugin XEP-0050 initialization")) self.host = host self.requesting = Sessions() self.answering = {} - host.bridge.addMethod("adHocRun", ".plugin", in_sign='sss', out_sign='s', - method=self._run, - async=True) - host.bridge.addMethod("adHocList", ".plugin", in_sign='ss', out_sign='s', - method=self._list, - async=True) - self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True) - host.importMenu((D_("Service"), D_("Commands")), self._commandsMenu, security_limit=2, help_string=D_("Execute ad-hoc commands")) + host.bridge.addMethod( + "adHocRun", + ".plugin", + in_sign="sss", + out_sign="s", + method=self._run, + async=True, + ) + host.bridge.addMethod( + "adHocList", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._list, + async=True, + ) + self.__requesting_id = host.registerCallback( + self._requestingEntity, with_data=True + ) + host.importMenu( + (D_("Service"), D_("Commands")), + self._commandsMenu, + security_limit=2, + help_string=D_("Execute ad-hoc commands"), + ) def getHandler(self, client): return XEP_0050_handler(self) def profileConnected(self, client): - self.addAdHocCommand(self._statusCallback, _("Status"), profile_key=client.profile) + self.addAdHocCommand( + self._statusCallback, _("Status"), profile_key=client.profile + ) def profileDisconnected(self, client): try: @@ -242,7 +336,7 @@ class XEP_0050(object): form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) if not no_instructions: - form_ui.addText(_("Please select a command"), 'instructions') + form_ui.addText(_("Please select a command"), "instructions") options = [(item.nodeIdentifier, item.name) for item in items] form_ui.addList("node", options) @@ -254,12 +348,12 @@ class XEP_0050(object): @param type_: note type (see XEP-0050 §4.3) @return: a C.XMLUI_DATA_LVL_* constant """ - if type_ == 'error': + if type_ == "error": return C.XMLUI_DATA_LVL_ERROR - elif type_ == 'warn': + elif type_ == "warn": return C.XMLUI_DATA_LVL_WARNING else: - if type_ != 'info': + if type_ != "info": log.warning(_(u"Invalid note type [%s], using info") % type_) return C.XMLUI_DATA_LVL_INFO @@ -269,10 +363,11 @@ class XEP_0050(object): @param notes (list): list of tuple (level, message) @return: list of messages """ - lvl_map = {C.XMLUI_DATA_LVL_INFO: '', - C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), - C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR") - } + lvl_map = { + C.XMLUI_DATA_LVL_INFO: "", + C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"), + C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR"), + } return [u"%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes] def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data): @@ -284,7 +379,7 @@ class XEP_0050(object): """ command_elt = iq_elt.elements(NS_COMMANDS, "command").next() - status = command_elt.getAttribute('status', XEP_0050.STATUS.EXECUTING) + status = command_elt.getAttribute("status", XEP_0050.STATUS.EXECUTING) if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]: # the command session is finished, we purge our session del self.requesting[session_id] @@ -292,27 +387,35 @@ class XEP_0050(object): session_id = None else: return None - remote_session_id = command_elt.getAttribute('sessionid') + remote_session_id = command_elt.getAttribute("sessionid") if remote_session_id: - session_data['remote_id'] = remote_session_id + session_data["remote_id"] = remote_session_id notes = [] - for note_elt in command_elt.elements(NS_COMMANDS, 'note'): - notes.append((self._getDataLvl(note_elt.getAttribute('type', 'info')), - unicode(note_elt))) - for data_elt in command_elt.elements(data_form.NS_X_DATA, 'x'): - if data_elt['type'] in ('form', 'result'): + for note_elt in command_elt.elements(NS_COMMANDS, "note"): + notes.append( + ( + self._getDataLvl(note_elt.getAttribute("type", "info")), + unicode(note_elt), + ) + ) + for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"): + if data_elt["type"] in ("form", "result"): break else: # no matching data element found if status != XEP_0050.STATUS.COMPLETED: - log.warning(_("No known payload found in ad-hoc command result, aborting")) + log.warning( + _("No known payload found in ad-hoc command result, aborting") + ) del self.requesting[session_id] - return xml_tools.XMLUI(C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: _("No payload found"), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR, - } - ) + return xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: _("No payload found"), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR, + }, + ) if not notes: # the status is completed, and we have no note to show return None @@ -321,13 +424,14 @@ class XEP_0050(object): # if we have more, we show a dialog with "info" level, and all notes merged dlg_level = notes[0][0] if len(notes) == 1 else C.XMLUI_DATA_LVL_INFO return xml_tools.XMLUI( - C.XMLUI_DIALOG, - dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: u'\n'.join(self._mergeNotes(notes)), - C.XMLUI_DATA_LVL: dlg_level, - }, - session_id = session_id - ) + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: u"\n".join(self._mergeNotes(notes)), + C.XMLUI_DATA_LVL: dlg_level, + }, + session_id=session_id, + ) if session_id is None: return xml_tools.dataFormEltResult2XMLUI(data_elt) @@ -338,8 +442,8 @@ class XEP_0050(object): def _requestingEntity(self, data, profile): def serialise(ret_data): - if 'xmlui' in ret_data: - ret_data['xmlui'] = ret_data['xmlui'].toXml() + if "xmlui" in ret_data: + ret_data["xmlui"] = ret_data["xmlui"].toXml() return ret_data d = self.requestingEntity(data, profile) @@ -354,7 +458,7 @@ class XEP_0050(object): @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error) """ - if C.bool(data.get('cancelled', C.BOOL_FALSE)): + if C.bool(data.get("cancelled", C.BOOL_FALSE)): return defer.succeed({}) client = self.host.getClient(profile) # TODO: cancel, prev and next are not managed @@ -363,49 +467,53 @@ class XEP_0050(object): if "session_id" not in data: # we just had the jid, we now request it for the available commands session_id, session_data = self.requesting.newSession(profile=client.profile) - entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid']) - session_data['jid'] = entity + entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX + "jid"]) + session_data["jid"] = entity d = self.list(client, entity) def sendItems(xmlui): - xmlui.session_id = session_id # we need to keep track of the session - return {'xmlui': xmlui} + xmlui.session_id = session_id # we need to keep track of the session + return {"xmlui": xmlui} d.addCallback(sendItems) else: # we have started a several forms sessions try: - session_data = self.requesting.profileGet(data["session_id"], client.profile) + session_data = self.requesting.profileGet( + data["session_id"], client.profile + ) except KeyError: - log.warning ("session id doesn't exist, session has probably expired") + log.warning("session id doesn't exist, session has probably expired") # TODO: send error dialog return defer.succeed({}) session_id = data["session_id"] - entity = session_data['jid'] + entity = session_data["jid"] try: - session_data['node'] + session_data["node"] # node has already been received except KeyError: # it's the first time we know the node, we save it in session data - session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node'] + session_data["node"] = data[xml_tools.SAT_FORM_PREFIX + "node"] # we request execute node's command - iq_elt = compat.IQ(client.xmlstream, 'set') - iq_elt['to'] = entity.full() + iq_elt = compat.IQ(client.xmlstream, "set") + iq_elt["to"] = entity.full() command_elt = iq_elt.addElement("command", NS_COMMANDS) - command_elt['node'] = session_data['node'] - command_elt['action'] = XEP_0050.ACTION.EXECUTE + command_elt["node"] = session_data["node"] + command_elt["action"] = XEP_0050.ACTION.EXECUTE try: # remote_id is the XEP_0050 sessionid used by answering command # while session_id is our own session id used with the frontend - command_elt['sessionid'] = session_data['remote_id'] + command_elt["sessionid"] = session_data["remote_id"] except KeyError: pass - command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload + command_elt.addChild( + xml_tools.XMLUIResultToElt(data) + ) # We add the XMLUI result to the command payload d = iq_elt.send() d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data) - d.addCallback(lambda xmlui: {'xmlui': xmlui} if xmlui is not None else {}) + d.addCallback(lambda xmlui: {"xmlui": xmlui} if xmlui is not None else {}) return d @@ -415,23 +523,27 @@ class XEP_0050(object): """ form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id) - form_ui.addText(_("Please enter target jid"), 'instructions') + form_ui.addText(_("Please enter target jid"), "instructions") form_ui.changeContainer("pairs") form_ui.addLabel("jid") form_ui.addString("jid", value=self.host.getClient(profile).jid.host) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} def _statusCallback(self, command_elt, session_data, action, node, profile): """ Ad-hoc command used to change the "show" part of status """ - actions = session_data.setdefault('actions',[]) + actions = session_data.setdefault("actions", []) actions.append(action) if len(actions) == 1: # it's our first request, we ask the desired new status status = XEP_0050.STATUS.EXECUTING - form = data_form.Form('form', title=_('status selection')) - show_options = [data_form.Option(name, label) for name, label in SHOWS.items()] - field = data_form.Field('list-single', 'show', options=show_options, required=True) + form = data_form.Form("form", title=_("status selection")) + show_options = [ + data_form.Option(name, label) for name, label in SHOWS.items() + ] + field = data_form.Field( + "list-single", "show", options=show_options, required=True + ) form.addField(field) payload = form.toElement() @@ -440,9 +552,9 @@ class XEP_0050(object): elif len(actions) == 2: # we should have the answer here try: - x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() + x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() answer_form = data_form.Form.fromElement(x_elt) - show = answer_form['show'] + show = answer_form["show"] except (KeyError, StopIteration): raise AdHocError(XEP_0050.ERROR.BAD_PAYLOAD) if show not in SHOWS: @@ -453,8 +565,8 @@ class XEP_0050(object): self.host.setPresence(show=show, profile_key=profile) # job done, we can end the session - form = data_form.Form('form', title=_(u'Updated')) - form.addField(data_form.Field('fixed', u'Status updated')) + form = data_form.Form("form", title=_(u"Updated")) + form.addField(data_form.Field("fixed", u"Status updated")) status = XEP_0050.STATUS.COMPLETED payload = None note = (self.NOTE.INFO, _(u"Status updated")) @@ -463,7 +575,7 @@ class XEP_0050(object): return (payload, status, None, note) - def _run(self, service_jid_s='', node='', profile_key=C.PROF_KEY_NONE): + def _run(self, service_jid_s="", node="", profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service_jid = jid.JID(service_jid_s) if service_jid_s else None d = self.run(client, service_jid, node or None) @@ -483,13 +595,15 @@ class XEP_0050(object): if service_jid is None: service_jid = jid.JID(client.jid.host) session_id, session_data = self.requesting.newSession(profile=client.profile) - session_data['jid'] = service_jid + session_data["jid"] = service_jid if node is None: xmlui = yield self.list(client, service_jid) else: - session_data['node'] = node - cb_data = yield self.requestingEntity({'session_id': session_id}, client.profile) - xmlui = cb_data['xmlui'] + session_data["node"] = node + cb_data = yield self.requestingEntity( + {"session_id": session_id}, client.profile + ) + xmlui = cb_data["xmlui"] xmlui.session_id = session_id defer.returnValue(xmlui) @@ -512,8 +626,20 @@ class XEP_0050(object): d.addCallback(self._items2XMLUI, no_instructions) return d - def addAdHocCommand(self, callback, label, node=None, features=None, timeout=600, allowed_jids=None, allowed_groups=None, - allowed_magics=None, forbidden_jids=None, forbidden_groups=None, profile_key=C.PROF_KEY_NONE): + def addAdHocCommand( + self, + callback, + label, + node=None, + features=None, + timeout=600, + allowed_jids=None, + allowed_groups=None, + allowed_magics=None, + forbidden_jids=None, + forbidden_groups=None, + profile_key=C.PROF_KEY_NONE, + ): """Add an ad-hoc command for the current profile @param callback: method associated with this ad-hoc command which return the payload data (see AdHocCommand._sendAnswer), can return a deferred @@ -534,7 +660,7 @@ class XEP_0050(object): # FIXME: "@ALL@" for profile_key seems useless and dangerous if node is None: - node = "%s_%s" % ('COMMANDS', uuid4()) + node = "%s_%s" % ("COMMANDS", uuid4()) if features is None: features = [data_form.NS_X_DATA] @@ -544,30 +670,46 @@ class XEP_0050(object): if allowed_groups is None: allowed_groups = [] if allowed_magics is None: - allowed_magics = ['@PROFILE_BAREJID@'] + allowed_magics = ["@PROFILE_BAREJID@"] if forbidden_jids is None: forbidden_jids = [] if forbidden_groups is None: forbidden_groups = [] for client in self.host.getClients(profile_key): - #TODO: manage newly created/removed profiles - _allowed_jids = (allowed_jids + [client.jid.userhostJID()]) if '@PROFILE_BAREJID@' in allowed_magics else allowed_jids - ad_hoc_command = AdHocCommand(self, callback, label, node, features, timeout, _allowed_jids, - allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client) + # TODO: manage newly created/removed profiles + _allowed_jids = ( + (allowed_jids + [client.jid.userhostJID()]) + if "@PROFILE_BAREJID@" in allowed_magics + else allowed_jids + ) + ad_hoc_command = AdHocCommand( + self, + callback, + label, + node, + features, + timeout, + _allowed_jids, + allowed_groups, + allowed_magics, + forbidden_jids, + forbidden_groups, + client, + ) ad_hoc_command.setHandlerParent(client) profile_commands = self.answering.setdefault(client.profile, {}) profile_commands[node] = ad_hoc_command def onCmdRequest(self, request, profile): request.handled = True - requestor = jid.JID(request['from']) - command_elt = request.elements(NS_COMMANDS, 'command').next() - action = command_elt.getAttribute('action', self.ACTION.EXECUTE) - node = command_elt.getAttribute('node') + requestor = jid.JID(request["from"]) + command_elt = request.elements(NS_COMMANDS, "command").next() + action = command_elt.getAttribute("action", self.ACTION.EXECUTE) + node = command_elt.getAttribute("node") if not node: raise exceptions.DataError - sessionid = command_elt.getAttribute('sessionid') + sessionid = command_elt.getAttribute("sessionid") try: command = self.answering[profile][node] except KeyError: @@ -582,18 +724,26 @@ class XEP_0050_handler(XMPPHandler): self.plugin_parent = plugin_parent def connectionInitialized(self): - self.xmlstream.addObserver(CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile) + self.xmlstream.addObserver( + CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): identities = [] - if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get(self.parent.profile): # we only add the identity if we have registred commands + if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get( + self.parent.profile + ): # we only add the identity if we have registred commands identities.append(ID_CMD_LIST) return [disco.DiscoFeature(NS_COMMANDS)] + identities - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): ret = [] if nodeIdentifier == NS_COMMANDS: - for command in self.plugin_parent.answering.get(self.parent.profile,{}).values(): + for command in self.plugin_parent.answering.get( + self.parent.profile, {} + ).values(): if command.isAuthorised(requestor): - ret.append(disco.DiscoItem(self.parent.jid, command.node, command.getName())) #TODO: manage name language + ret.append( + disco.DiscoItem(self.parent.jid, command.node, command.getName()) + ) # TODO: manage name language return ret diff --git a/sat/plugins/plugin_xep_0054.py b/sat/plugins/plugin_xep_0054.py index 4e7a6471..73637bf7 100755 --- a/sat/plugins/plugin_xep_0054.py +++ b/sat/plugins/plugin_xep_0054.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import threads, defer from twisted.words.protocols.jabber import jid, error @@ -36,10 +37,13 @@ from hashlib import sha1 from sat.core import exceptions from sat.memory import persistent import mimetypes + try: from PIL import Image except: - raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") + raise exceptions.MissingModule( + u"Missing module pillow, please download/install it from https://python-pillow.github.io" + ) from cStringIO import StringIO try: @@ -48,17 +52,17 @@ except ImportError: from wokkel.subprotocols import XMPPHandler AVATAR_PATH = "avatars" -AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! +AVATAR_DIM = (64, 64) # FIXME: dim are not adapted to modern resolutions ! IQ_GET = '/iq[@type="get"]' -NS_VCARD = 'vcard-temp' +NS_VCARD = "vcard-temp" VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' # TODO: manage requests -PRESENCE = '/presence' -NS_VCARD_UPDATE = 'vcard-temp:x:update' +PRESENCE = "/presence" +NS_VCARD_UPDATE = "vcard-temp:x:update" VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]' -CACHED_DATA = {'avatar', 'nick'} +CACHED_DATA = {"avatar", "nick"} MAX_AGE = 60 * 60 * 24 * 365 PLUGIN_INFO = { @@ -70,20 +74,34 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["XEP-0045"], C.PI_MAIN: "XEP_0054", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of vcard-temp""") + C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""), } class XEP_0054(object): - #TODO: - check that nickname is ok + # TODO: - check that nickname is ok # - refactor the code/better use of Wokkel # - get missing values def __init__(self, host): log.info(_(u"Plugin XEP_0054 initialization")) self.host = host - host.bridge.addMethod(u"avatarGet", u".plugin", in_sign=u'sbbs', out_sign=u's', method=self._getAvatar, async=True) - host.bridge.addMethod(u"avatarSet", u".plugin", in_sign=u'ss', out_sign=u'', method=self._setAvatar, async=True) + host.bridge.addMethod( + u"avatarGet", + u".plugin", + in_sign=u"sbbs", + out_sign=u"s", + method=self._getAvatar, + async=True, + ) + host.bridge.addMethod( + u"avatarSet", + u".plugin", + in_sign=u"ss", + out_sign=u"", + method=self._setAvatar, + async=True, + ) host.trigger.add(u"presence_available", self.presenceAvailableTrigger) host.memory.setSignalOnUpdate(u"avatar") host.memory.setSignalOnUpdate(u"nick") @@ -98,7 +116,7 @@ class XEP_0054(object): @return (bool): True if the bare jid of the entity is a room jid """ try: - muc_plg = self.host.plugins['XEP-0045'] + muc_plg = self.host.plugins["XEP-0045"] except KeyError: return False @@ -123,12 +141,12 @@ class XEP_0054(object): def presenceAvailableTrigger(self, presence_elt, client): if client.jid.userhost() in client._cache_0054: try: - avatar_hash = client._cache_0054[client.jid.userhost()]['avatar'] + avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"] except KeyError: log.info(u"No avatar in cache for {}".format(client.jid.userhost())) return True - x_elt = domish.Element((NS_VCARD_UPDATE, 'x')) - x_elt.addElement('photo', content=avatar_hash) + x_elt = domish.Element((NS_VCARD_UPDATE, "x")) + x_elt.addElement("photo", content=avatar_hash) presence_elt.addChild(x_elt) return True @@ -139,7 +157,7 @@ class XEP_0054(object): self._fillCachedValues(client.profile) def _fillCachedValues(self, profile): - #FIXME: this may need to be reworked + # FIXME: this may need to be reworked # the current naive approach keeps a map between all jids # in persistent cache, then put avatar hashs in memory. # Hashes should be shared between profiles (or not ? what @@ -153,9 +171,15 @@ class XEP_0054(object): try: value = data[name] if value is None: - log.error(u"{name} value for {jid_} is None, ignoring".format(name=name, jid_=jid_)) + log.error( + u"{name} value for {jid_} is None, ignoring".format( + name=name, jid_=jid_ + ) + ) continue - self.host.memory.updateEntityData(jid_, name, data[name], silent=True, profile_key=profile) + self.host.memory.updateEntityData( + jid_, name, data[name], silent=True, profile_key=profile + ) except KeyError: pass @@ -184,7 +208,9 @@ class XEP_0054(object): else: client._cache_0054.force(jid_s) else: - self.host.memory.updateEntityData(jid_, name, value, profile_key=client.profile) + self.host.memory.updateEntityData( + jid_, name, value, profile_key=client.profile + ) if name in CACHED_DATA: client._cache_0054.setdefault(jid_s, {})[name] = value client._cache_0054.force(jid_s) @@ -206,7 +232,7 @@ class XEP_0054(object): """Parse a - """.encode('utf-8')) + """.encode( + "utf-8" + ) + ) output = """photo_elt and save the picture""" # XXX: this method is launched in a separate thread try: - mime_type = unicode(photo_elt.elements(NS_VCARD, 'TYPE').next()) + mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next()) except StopIteration: log.warning(u"no MIME type found, assuming image/png") mime_type = u"image/png" @@ -220,35 +246,41 @@ class XEP_0054(object): mime_type = "image/png" else: # TODO: handle other image formats (svg?) - log.warning(u"following avatar image format is not handled: {type} [{jid}]".format( - type=mime_type, jid=entity_jid.full())) + log.warning( + u"following avatar image format is not handled: {type} [{jid}]".format( + type=mime_type, jid=entity_jid.full() + ) + ) raise Failure(exceptions.DataError()) ext = mimetypes.guess_extension(mime_type, strict=False) assert ext is not None - if ext == u'.jpe': - ext = u'.jpg' - log.debug(u'photo of type {type} with extension {ext} found [{jid}]'.format( - type=mime_type, ext=ext, jid=entity_jid.full())) + if ext == u".jpe": + ext = u".jpg" + log.debug( + u"photo of type {type} with extension {ext} found [{jid}]".format( + type=mime_type, ext=ext, jid=entity_jid.full() + ) + ) try: - buf = str(photo_elt.elements(NS_VCARD, 'BINVAL').next()) + buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next()) except StopIteration: log.warning(u"BINVAL element not found") raise Failure(exceptions.NotFound()) if not buf: log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full())) raise Failure(exceptions.NotFound()) - log.debug(_(u'Decoding binary')) + log.debug(_(u"Decoding binary")) decoded = b64decode(buf) del buf image_hash = sha1(decoded).hexdigest() with client.cache.cacheData( - PLUGIN_INFO['import_name'], + PLUGIN_INFO["import_name"], image_hash, mime_type, # we keep in cache for 1 year - MAX_AGE - ) as f: + MAX_AGE, + ) as f: f.write(decoded) return image_hash @@ -259,39 +291,44 @@ class XEP_0054(object): vcard_dict = {} for elem in vcard.elements(): - if elem.name == 'FN': - vcard_dict['fullname'] = unicode(elem) - elif elem.name == 'NICKNAME': - vcard_dict['nick'] = unicode(elem) - self.updateCache(client, entity_jid, 'nick', vcard_dict['nick']) - elif elem.name == 'URL': - vcard_dict['website'] = unicode(elem) - elif elem.name == 'EMAIL': - vcard_dict['email'] = unicode(elem) - elif elem.name == 'BDAY': - vcard_dict['birthday'] = unicode(elem) - elif elem.name == 'PHOTO': + if elem.name == "FN": + vcard_dict["fullname"] = unicode(elem) + elif elem.name == "NICKNAME": + vcard_dict["nick"] = unicode(elem) + self.updateCache(client, entity_jid, "nick", vcard_dict["nick"]) + elif elem.name == "URL": + vcard_dict["website"] = unicode(elem) + elif elem.name == "EMAIL": + vcard_dict["email"] = unicode(elem) + elif elem.name == "BDAY": + vcard_dict["birthday"] = unicode(elem) + elif elem.name == "PHOTO": # TODO: handle EXTVAL try: avatar_hash = yield threads.deferToThread( - self.savePhoto, client, elem, entity_jid) + self.savePhoto, client, elem, entity_jid + ) except (exceptions.DataError, exceptions.NotFound) as e: - avatar_hash = '' - vcard_dict['avatar'] = avatar_hash + avatar_hash = "" + vcard_dict["avatar"] = avatar_hash except Exception as e: log.error(u"avatar saving error: {}".format(e)) avatar_hash = None else: - vcard_dict['avatar'] = avatar_hash - self.updateCache(client, entity_jid, 'avatar', avatar_hash) + vcard_dict["avatar"] = avatar_hash + self.updateCache(client, entity_jid, "avatar", avatar_hash) else: - log.debug(u'FIXME: [{}] VCard tag is not managed yet'.format(elem.name)) + log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name)) # if a data in cache doesn't exist anymore, we need to delete it # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys) # and we reset them for datum in CACHED_DATA.difference(vcard_dict.keys()): - log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full())) + log.debug( + u"reseting vcard datum [{datum}] for {entity}".format( + datum=datum, entity=entity_jid.full() + ) + ) self.updateCache(client, entity_jid, datum, None) defer.returnValue(vcard_dict) @@ -309,11 +346,15 @@ class XEP_0054(object): def _vCardEb(self, failure_, to_jid, client): """Called when something is wrong with registration""" - log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_)) + log.warning( + u"Can't get vCard for {jid}: {failure}".format( + jid=to_jid.full, failure=failure_ + ) + ) self.updateCache(client, to_jid, "avatar", None) def _getVcardElt(self, iq_elt): - return iq_elt.elements(NS_VCARD, "vCard").next() + return iq_elt.elements(NS_VCARD, "vCard").next() def getCardRaw(self, client, entity_jid): """get raw vCard XML @@ -322,10 +363,10 @@ class XEP_0054(object): """ entity_jid = self.getBareOrFull(client, entity_jid) log.debug(u"Asking for {}'s VCard".format(entity_jid.full())) - reg_request = client.IQ('get') + reg_request = client.IQ("get") reg_request["from"] = client.jid.full() reg_request["to"] = entity_jid.full() - reg_request.addElement('vCard', NS_VCARD) + reg_request.addElement("vCard", NS_VCARD) d = reg_request.send(entity_jid.full()) d.addCallback(self._getVcardElt) return d @@ -337,19 +378,24 @@ class XEP_0054(object): @result: id to retrieve the profile """ d = self.getCardRaw(client, entity_jid) - d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client]) + d.addCallbacks( + self._vCardCb, + self._vCardEb, + callbackArgs=[entity_jid, client], + errbackArgs=[entity_jid, client], + ) return d def _getCardCb(self, dummy, client, entity): try: - return client._cache_0054[entity.full()]['avatar'] + return client._cache_0054[entity.full()]["avatar"] except KeyError: raise Failure(exceptions.NotFound()) def _getAvatar(self, entity, cache_only, hash_only, profile): client = self.host.getClient(profile) d = self.getAvatar(client, jid.JID(entity), cache_only, hash_only) - d.addErrback(lambda dummy: '') + d.addErrback(lambda dummy: "") return d @@ -370,7 +416,7 @@ class XEP_0054(object): try: # we first check if we have avatar in cache - avatar_hash = client._cache_0054[entity.full()]['avatar'] + avatar_hash = client._cache_0054[entity.full()]["avatar"] if avatar_hash: # avatar is known and exists full_path = client.cache.getFilePath(avatar_hash) @@ -379,7 +425,7 @@ class XEP_0054(object): raise KeyError else: # avatar has already been checked but it is not set - full_path = u'' + full_path = u"" except KeyError: # avatar is not in cache if cache_only: @@ -406,11 +452,11 @@ class XEP_0054(object): @param entity(jid.JID): entity to get nick from @return(unicode, None): nick or None if not found """ - nick = self.getCache(client, entity, u'nick') + nick = self.getCache(client, entity, u"nick") if nick is not None: defer.returnValue(nick) yield self.getCard(client, entity) - defer.returnValue(self.getCache(client, entity, u'nick')) + defer.returnValue(self.getCache(client, entity, u"nick")) @defer.inlineCallbacks def setNick(self, client, nick): @@ -422,22 +468,22 @@ class XEP_0054(object): try: vcard_elt = yield self.getCardRaw(client, jid_) except error.StanzaError as e: - if e.condition == 'item-not-found': - vcard_elt = domish.Element((NS_VCARD, 'vCard')) + if e.condition == "item-not-found": + vcard_elt = domish.Element((NS_VCARD, "vCard")) else: raise e try: - nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME')) + nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME")) except StopIteration: pass else: vcard_elt.children.remove(nickname_elt) - nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick) + nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick) iq_elt = client.IQ() vcard_elt = iq_elt.addChild(vcard_elt) yield iq_elt.send() - self.updateCache(client, jid_, u'nick', unicode(nick)) + self.updateCache(client, jid_, u"nick", unicode(nick)) def _buildSetAvatar(self, client, vcard_elt, file_path): # XXX: this method is executed in a separate thread @@ -460,18 +506,15 @@ class XEP_0054(object): right -= offset img = img.crop((left, upper, right, lower)) img_buf = StringIO() - img.save(img_buf, 'PNG') + img.save(img_buf, "PNG") - photo_elt = vcard_elt.addElement('PHOTO') - photo_elt.addElement('TYPE', content='image/png') - photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue())) + photo_elt = vcard_elt.addElement("PHOTO") + photo_elt.addElement("TYPE", content="image/png") + photo_elt.addElement("BINVAL", content=b64encode(img_buf.getvalue())) image_hash = sha1(img_buf.getvalue()).hexdigest() with client.cache.cacheData( - PLUGIN_INFO['import_name'], - image_hash, - "image/png", - MAX_AGE - ) as f: + PLUGIN_INFO["import_name"], image_hash, "image/png", MAX_AGE + ) as f: f.write(img_buf.getvalue()) return image_hash @@ -489,14 +532,14 @@ class XEP_0054(object): # we first check if a vcard already exists, to keep data vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID()) except error.StanzaError as e: - if e.condition == 'item-not-found': - vcard_elt = domish.Element((NS_VCARD, 'vCard')) + if e.condition == "item-not-found": + vcard_elt = domish.Element((NS_VCARD, "vCard")) else: raise e else: # the vcard exists, we need to remove PHOTO element as we'll make a new one try: - photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO')) + photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO")) except StopIteration: pass else: @@ -504,12 +547,14 @@ class XEP_0054(object): iq_elt = client.IQ() iq_elt.addChild(vcard_elt) - image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path) + image_hash = yield threads.deferToThread( + self._buildSetAvatar, client, vcard_elt, file_path + ) # image is now at the right size/format - self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash) + self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash) yield iq_elt.send() - client.presence.available() # FIXME: should send the current presence, not always "available" ! + client.presence.available() # FIXME: should send the current presence, not always "available" ! class XEP_0054_handler(XMPPHandler): @@ -522,21 +567,24 @@ class XEP_0054_handler(XMPPHandler): def connectionInitialized(self): self.xmlstream.addObserver(VCARD_UPDATE, self.update) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_VCARD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def _checkAvatarHash(self, dummy, client, entity, given_hash): """check that hash in cash (i.e. computed hash) is the same as given one""" # XXX: if they differ, the avater will be requested on each connection # TODO: try to avoid re-requesting avatar in this case - computed_hash = self.plugin_parent.getCache(client, entity, 'avatar') + computed_hash = self.plugin_parent.getCache(client, entity, "avatar") if computed_hash != given_hash: - log.warning(u"computed hash differs from given hash for {entity}:\n" + log.warning( + u"computed hash differs from given hash for {entity}:\n" "computed: {computed}\ngiven: {given}".format( - entity=entity, computed=computed_hash, given=given_hash)) + entity=entity, computed=computed_hash, given=given_hash + ) + ) def update(self, presence): """Called on stanza with vcard data @@ -545,22 +593,22 @@ class XEP_0054_handler(XMPPHandler): @param presend(domish.Element): stanza """ client = self.parent - entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence['from'])) - #FIXME: wokkel's data_form should be used here + entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"])) + # FIXME: wokkel's data_form should be used here try: - x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next() + x_elt = presence.elements(NS_VCARD_UPDATE, "x").next() except StopIteration: return try: - photo_elt = x_elt.elements(NS_VCARD_UPDATE, 'photo').next() + photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next() except StopIteration: return hash_ = unicode(photo_elt).strip() if hash_ == C.HASH_SHA1_EMPTY: - hash_ = u'' - old_avatar = self.plugin_parent.getCache(client, entity_jid, 'avatar') + hash_ = u"" + old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar") if old_avatar == hash_: # no change, we can return... @@ -568,7 +616,11 @@ class XEP_0054_handler(XMPPHandler): # ...but we double check that avatar is in cache file_path = client.cache.getFilePath(hash_) if file_path is None: - log.error(u"Avatar for [{}] should be in cache but it is not! We get it".format(entity_jid.full())) + log.error( + u"Avatar for [{}] should be in cache but it is not! We get it".format( + entity_jid.full() + ) + ) self.plugin_parent.getCard(client, entity_jid) else: log.debug(u"avatar for {} already in cache".format(entity_jid.full())) @@ -578,14 +630,20 @@ class XEP_0054_handler(XMPPHandler): # the avatar has been removed # XXX: we use empty string instead of None to indicate that we took avatar # but it is empty on purpose - self.plugin_parent.updateCache(client, entity_jid, 'avatar', '') + self.plugin_parent.updateCache(client, entity_jid, "avatar", "") return file_path = client.cache.getFilePath(hash_) if file_path is not None: - log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(entity_jid.full())) - self.plugin_parent.updateCache(client, entity_jid, 'avatar', hash_) + log.debug( + u"New avatar found for [{}], it's already in cache, we use it".format( + entity_jid.full() + ) + ) + self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_) else: - log.debug(u'New avatar found for [{}], requesting vcard'.format(entity_jid.full())) + log.debug( + u"New avatar found for [{}], requesting vcard".format(entity_jid.full()) + ) d = self.plugin_parent.getCard(client, entity_jid) d.addCallback(self._checkAvatarHash, client, entity_jid, hash_) diff --git a/sat/plugins/plugin_xep_0055.py b/sat/plugins/plugin_xep_0055.py index 0017e5b6..f88d5bf5 100644 --- a/sat/plugins/plugin_xep_0055.py +++ b/sat/plugins/plugin_xep_0055.py @@ -19,6 +19,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber.xmlstream import IQ @@ -30,6 +31,7 @@ from sat.core.exceptions import DataError from sat.tools import xml_tools from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -37,7 +39,7 @@ except ImportError: from zope.interface import implements -NS_SEARCH = 'jabber:iq:search' +NS_SEARCH = "jabber:iq:search" PLUGIN_INFO = { C.PI_NAME: "Jabber Search", @@ -48,7 +50,7 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["XEP-0059"], C.PI_MAIN: "XEP_0055", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of Jabber Search""") + C.PI_DESCRIPTION: _("""Implementation of Jabber Search"""), } # config file parameters @@ -58,26 +60,48 @@ CONFIG_SERVICE_LIST = "service_list" DEFAULT_SERVICE_LIST = ["salut.libervia.org"] FIELD_SINGLE = "field_single" # single text field for the simple search -FIELD_CURRENT_SERVICE = "current_service_jid" # read-only text field for the advanced search +FIELD_CURRENT_SERVICE = ( + "current_service_jid" +) # read-only text field for the advanced search + class XEP_0055(object): - def __init__(self, host): log.info(_("Jabber search plugin initialization")) self.host = host # default search services (config file + hard-coded lists) - self.services = [jid.JID(entry) for entry in host.memory.getConfig(CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST)] + self.services = [ + jid.JID(entry) + for entry in host.memory.getConfig( + CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST + ) + ] - host.bridge.addMethod("searchGetFieldsUI", ".plugin", in_sign='ss', out_sign='s', - method=self._getFieldsUI, - async=True) - host.bridge.addMethod("searchRequest", ".plugin", in_sign='sa{ss}s', out_sign='s', - method=self._searchRequest, - async=True) + host.bridge.addMethod( + "searchGetFieldsUI", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._getFieldsUI, + async=True, + ) + host.bridge.addMethod( + "searchRequest", + ".plugin", + in_sign="sa{ss}s", + out_sign="s", + method=self._searchRequest, + async=True, + ) self.__search_menu_id = host.registerCallback(self._getMainUI, with_data=True) - host.importMenu((D_("Contacts"), D_("Search directory")), self._getMainUI, security_limit=1, help_string=D_("Search user directory")) + host.importMenu( + (D_("Contacts"), D_("Search directory")), + self._getMainUI, + security_limit=1, + help_string=D_("Search user directory"), + ) def _getHostServices(self, profile): """Return the jabber search services associated to the user host. @@ -89,10 +113,8 @@ class XEP_0055(object): d = self.host.findFeaturesSet(client, [NS_SEARCH]) return d.addCallback(lambda set_: list(set_)) - ## Main search UI (menu item callback) ## - def _getMainUI(self, raw_data, profile): """Get the XMLUI for selecting a service and searching the directory. @@ -115,11 +137,18 @@ class XEP_0055(object): # extend services offered by user's server with the default services services.extend([service for service in self.services if service not in services]) data = xml_tools.XMLUIResult2DataFormResult(raw_data) - main_ui = xml_tools.XMLUI(C.XMLUI_WINDOW, container="tabs", title=_("Search users"), submit_id=self.__search_menu_id) + main_ui = xml_tools.XMLUI( + C.XMLUI_WINDOW, + container="tabs", + title=_("Search users"), + submit_id=self.__search_menu_id, + ) d = self._addSimpleSearchUI(services, main_ui, data, profile) - d.addCallback(lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile)) - return d.addCallback(lambda dummy: {'xmlui': main_ui.toXml()}) + d.addCallback( + lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile) + ) + return d.addCallback(lambda dummy: {"xmlui": main_ui.toXml()}) def _addSimpleSearchUI(self, services, main_ui, data, profile): """Add to the main UI a tab for the simple search. @@ -133,32 +162,49 @@ class XEP_0055(object): @return: a dummy Deferred """ - service_jid = services[0] # TODO: search on all the given services, not only the first one + service_jid = services[ + 0 + ] # TODO: search on all the given services, not only the first one - form = data_form.Form('form', formNamespace=NS_SEARCH) - form.addField(data_form.Field('text-single', FIELD_SINGLE, label=_('Search for'), value=data.get(FIELD_SINGLE, ''))) + form = data_form.Form("form", formNamespace=NS_SEARCH) + form.addField( + data_form.Field( + "text-single", + FIELD_SINGLE, + label=_("Search for"), + value=data.get(FIELD_SINGLE, ""), + ) + ) - sub_cont = main_ui.main_container.addTab("simple_search", label=_("Simple search"), container=xml_tools.VerticalContainer) + sub_cont = main_ui.main_container.addTab( + "simple_search", + label=_("Simple search"), + container=xml_tools.VerticalContainer, + ) main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui))) xml_tools.dataForm2Widgets(main_ui, form) # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") main_ui.addButton(self.__search_menu_id, _("Search"), (FIELD_SINGLE,)) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button - simple_data = {key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,)} + simple_data = { + key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,) + } if simple_data: log.debug("Simple search with %s on %s" % (simple_data, service_jid)) sub_cont.parent.setSelected(True) main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) - main_ui.addDivider('dash') + main_ui.addDivider("dash") d = self.searchRequest(service_jid, simple_data, profile) - d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt), - lambda failure: main_ui.addText(failure.getErrorMessage())) + d.addCallbacks( + lambda elt: self._displaySearchResult(main_ui, elt), + lambda failure: main_ui.addText(failure.getErrorMessage()), + ) return d return defer.succeed(None) @@ -175,16 +221,20 @@ class XEP_0055(object): @return: a dummy Deferred """ - sub_cont = main_ui.main_container.addTab("advanced_search", label=_("Advanced search"), container=xml_tools.VerticalContainer) - service_selection_fields = ['service_jid', 'service_jid_extra'] + sub_cont = main_ui.main_container.addTab( + "advanced_search", + label=_("Advanced search"), + container=xml_tools.VerticalContainer, + ) + service_selection_fields = ["service_jid", "service_jid_extra"] - if 'service_jid_extra' in data: + if "service_jid_extra" in data: # refresh button has been pushed, select the tab sub_cont.parent.setSelected(True) # get the selected service - service_jid_s = data.get('service_jid_extra', '') + service_jid_s = data.get("service_jid_extra", "") if not service_jid_s: - service_jid_s = data.get('service_jid', unicode(services[0])) + service_jid_s = data.get("service_jid", unicode(services[0])) log.debug("Refreshing search fields for %s" % service_jid_s) else: service_jid_s = data.get(FIELD_CURRENT_SERVICE, unicode(services[0])) @@ -194,27 +244,32 @@ class XEP_0055(object): main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui))) main_ui.addLabel(_("Search on")) - main_ui.addList('service_jid', options=services_s, selected=service_jid_s) + main_ui.addList("service_jid", options=services_s, selected=service_jid_s) main_ui.addLabel(_("Other service")) - main_ui.addString(name='service_jid_extra') + main_ui.addString(name="service_jid_extra") # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') - main_ui.addButton(self.__search_menu_id, _("Refresh fields"), service_selection_fields) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") + main_ui.addButton( + self.__search_menu_id, _("Refresh fields"), service_selection_fields + ) + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button main_ui.addLabel(_("Displaying the search form for")) main_ui.addString(name=FIELD_CURRENT_SERVICE, value=service_jid_s, read_only=True) - main_ui.addDivider('dash') - main_ui.addDivider('dash') + main_ui.addDivider("dash") + main_ui.addDivider("dash") main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) service_jid = jid.JID(service_jid_s) d = self.getFieldsUI(service_jid, profile) - d.addCallbacks(self._addAdvancedForm, lambda failure: main_ui.addText(failure.getErrorMessage()), - [service_jid, main_ui, sub_cont, data, profile]) + d.addCallbacks( + self._addAdvancedForm, + lambda failure: main_ui.addText(failure.getErrorMessage()), + [service_jid, main_ui, sub_cont, data, profile], + ) return d def _addAdvancedForm(self, form_elt, service_jid, main_ui, sub_cont, data, profile): @@ -243,26 +298,29 @@ class XEP_0055(object): widget.setAttribute("value", adv_data[name]) # FIXME: add colspan attribute to divider? (we are in a PairsContainer) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # here we added a blank line before the button - main_ui.addDivider('blank') - main_ui.addButton(self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE]) - main_ui.addDivider('blank') - main_ui.addDivider('blank') # a blank line again after the button + main_ui.addDivider("blank") + main_ui.addDivider("blank") # here we added a blank line before the button + main_ui.addDivider("blank") + main_ui.addButton( + self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE] + ) + main_ui.addDivider("blank") + main_ui.addDivider("blank") # a blank line again after the button if adv_data: # display the search results log.debug("Advanced search with %s on %s" % (adv_data, service_jid)) sub_cont.parent.setSelected(True) main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) - main_ui.addDivider('dash') + main_ui.addDivider("dash") d = self.searchRequest(service_jid, adv_data, profile) - d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt), - lambda failure: main_ui.addText(failure.getErrorMessage())) + d.addCallbacks( + lambda elt: self._displaySearchResult(main_ui, elt), + lambda failure: main_ui.addText(failure.getErrorMessage()), + ) return d return defer.succeed(None) - def _displaySearchResult(self, main_ui, elt): """Display the search results. @@ -277,7 +335,9 @@ class XEP_0055(object): header = headers.keys()[i % len(headers)] widget_type, widget_args, widget_kwargs = xmlui_data[i] value = widget_args[0] - values.setdefault(header, []).append(jid.JID(value) if header == "jid" else value) + values.setdefault(header, []).append( + jid.JID(value) if header == "jid" else value + ) main_ui.addJidsList(jids=values["jid"], name=D_(u"Search results")) # TODO: also display the values other than JID else: @@ -285,10 +345,8 @@ class XEP_0055(object): else: main_ui.addText(D_("The search gave no result")) - ## Retrieve the search fields ## - def _getFieldsUI(self, to_jid_s, profile_key): """Ask a service to send us the list of the form fields it manages. @@ -308,10 +366,10 @@ class XEP_0055(object): @return: a deferred domish.Element """ client = self.host.getClient(profile_key) - fields_request = IQ(client.xmlstream, 'get') + fields_request = IQ(client.xmlstream, "get") fields_request["from"] = client.jid.full() fields_request["to"] = to_jid.full() - fields_request.addElement('query', NS_SEARCH) + fields_request.addElement("query", NS_SEARCH) d = fields_request.send(to_jid.full()) d.addCallbacks(self._getFieldsUICb, self._getFieldsUIEb) return d @@ -323,15 +381,17 @@ class XEP_0055(object): @return: domish.Element """ try: - query_elts = answer.elements('jabber:iq:search', 'query').next() + query_elts = answer.elements("jabber:iq:search", "query").next() except StopIteration: log.info(_("No query element found")) raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC try: - form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next() + form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() except StopIteration: log.info(_("No data form found")) - raise NotImplementedError("Only search through data form is implemented so far") + raise NotImplementedError( + "Only search through data form is implemented so far" + ) return form_elt def _getFieldsUIEb(self, failure): @@ -343,10 +403,8 @@ class XEP_0055(object): log.info(_("Fields request failure: %s") % unicode(failure.getErrorMessage())) raise failure - ## Do the search ## - def _searchRequest(self, to_jid_s, search_data, profile_key): """Actually do a search, according to filled data. @@ -370,15 +428,17 @@ class XEP_0055(object): if FIELD_SINGLE in search_data: value = search_data[FIELD_SINGLE] d = self.getFieldsUI(to_jid, profile_key) - d.addCallback(lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key)) + d.addCallback( + lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key) + ) return d client = self.host.getClient(profile_key) - search_request = IQ(client.xmlstream, 'set') + search_request = IQ(client.xmlstream, "set") search_request["from"] = client.jid.full() search_request["to"] = to_jid.full() - query_elt = search_request.addElement('query', NS_SEARCH) - x_form = data_form.Form('submit', formNamespace=NS_SEARCH) + query_elt = search_request.addElement("query", NS_SEARCH) + x_form = data_form.Form("submit", formNamespace=NS_SEARCH) x_form.makeFields(search_data) query_elt.addChild(x_form.toElement()) # TODO: XEP-0059 could be used here (with the needed new method attributes) @@ -406,13 +466,17 @@ class XEP_0055(object): for success, form_elt in result: if not success: continue - if result_elt is None: # the result element is built over the first answer + if ( + result_elt is None + ): # the result element is built over the first answer result_elt = form_elt continue - for item_elt in form_elt.elements('jabber:x:data', 'item'): + for item_elt in form_elt.elements("jabber:x:data", "item"): result_elt.addChild(item_elt) if result_elt is None: - raise defer.failure.Failure(DataError(_("The search could not be performed"))) + raise defer.failure.Failure( + DataError(_("The search could not be performed")) + ) return result_elt return defer.DeferredList(d_list).addCallback(cb) @@ -424,15 +488,17 @@ class XEP_0055(object): @return: domish.Element """ try: - query_elts = answer.elements('jabber:iq:search', 'query').next() + query_elts = answer.elements("jabber:iq:search", "query").next() except StopIteration: log.info(_("No query element found")) raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC try: - form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next() + form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() except StopIteration: log.info(_("No data form found")) - raise NotImplementedError("Only search through data form is implemented so far") + raise NotImplementedError( + "Only search through data form is implemented so far" + ) return form_elt def _searchErr(self, failure): @@ -453,9 +519,8 @@ class XEP_0055_handler(XMPPHandler): self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_SEARCH)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] - diff --git a/sat/plugins/plugin_xep_0059.py b/sat/plugins/plugin_xep_0059.py index 7e68afa1..f7791159 100644 --- a/sat/plugins/plugin_xep_0059.py +++ b/sat/plugins/plugin_xep_0059.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco @@ -38,7 +39,7 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0059"], C.PI_MAIN: "XEP_0059", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Result Set Management""") + C.PI_DESCRIPTION: _("""Implementation of Result Set Management"""), } @@ -55,8 +56,8 @@ class XEP_0059(object): class XEP_0059_handler(xmlstream.XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(rsm.NS_RSM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0060.py b/sat/plugins/plugin_xep_0060.py index cf2993f0..6e035645 100755 --- a/sat/plugins/plugin_xep_0060.py +++ b/sat/plugins/plugin_xep_0060.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import sat_defer @@ -33,6 +34,7 @@ from collections import namedtuple import urllib import datetime from dateutil import tz + # XXX: sat_tmp.wokkel.pubsub is actually use instead of wokkel version # mam and rsm come from sat_tmp.wokkel too from wokkel import pubsub @@ -49,70 +51,226 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["XEP-0313"], C.PI_MAIN: "XEP_0060", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol""") + C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol"""), } UNSPECIFIED = "unspecified error" MAM_FILTER = "mam_filter_" -Extra = namedtuple('Extra', ('rsm_request', 'extra')) +Extra = namedtuple("Extra", ("rsm_request", "extra")) # rsm_request is the rsm.RSMRequest build with rsm_ prefixed keys, or None # extra is a potentially empty dict class XEP_0060(object): - OPT_ACCESS_MODEL = 'pubsub#access_model' - OPT_PERSIST_ITEMS = 'pubsub#persist_items' - OPT_MAX_ITEMS = 'pubsub#max_items' - OPT_DELIVER_PAYLOADS = 'pubsub#deliver_payloads' - OPT_SEND_ITEM_SUBSCRIBE = 'pubsub#send_item_subscribe' - OPT_NODE_TYPE = 'pubsub#node_type' - OPT_SUBSCRIPTION_TYPE = 'pubsub#subscription_type' - OPT_SUBSCRIPTION_DEPTH = 'pubsub#subscription_depth' - OPT_ROSTER_GROUPS_ALLOWED = 'pubsub#roster_groups_allowed' - OPT_PUBLISH_MODEL = 'pubsub#publish_model' - ACCESS_OPEN = 'open' - ACCESS_PRESENCE = 'presence' - ACCESS_ROSTER = 'roster' - ACCESS_PUBLISHER_ROSTER = 'publisher-roster' - ACCESS_AUTHORIZE = 'authorize' - ACCESS_WHITELIST = 'whitelist' + OPT_ACCESS_MODEL = "pubsub#access_model" + OPT_PERSIST_ITEMS = "pubsub#persist_items" + OPT_MAX_ITEMS = "pubsub#max_items" + OPT_DELIVER_PAYLOADS = "pubsub#deliver_payloads" + OPT_SEND_ITEM_SUBSCRIBE = "pubsub#send_item_subscribe" + OPT_NODE_TYPE = "pubsub#node_type" + OPT_SUBSCRIPTION_TYPE = "pubsub#subscription_type" + OPT_SUBSCRIPTION_DEPTH = "pubsub#subscription_depth" + OPT_ROSTER_GROUPS_ALLOWED = "pubsub#roster_groups_allowed" + OPT_PUBLISH_MODEL = "pubsub#publish_model" + ACCESS_OPEN = "open" + ACCESS_PRESENCE = "presence" + ACCESS_ROSTER = "roster" + ACCESS_PUBLISHER_ROSTER = "publisher-roster" + ACCESS_AUTHORIZE = "authorize" + ACCESS_WHITELIST = "whitelist" def __init__(self, host): log.info(_(u"PubSub plugin initialization")) self.host = host - self._mam = host.plugins.get('XEP-0313') - self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks) + self._mam = host.plugins.get("XEP-0313") + self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks) self.rt_sessions = sat_defer.RTDeferredSessions() - host.bridge.addMethod("psNodeCreate", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._createNode, async=True) - host.bridge.addMethod("psNodeConfigurationGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeConfiguration, async=True) - host.bridge.addMethod("psNodeConfigurationSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeConfiguration, async=True) - host.bridge.addMethod("psNodeAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeAffiliations, async=True) - host.bridge.addMethod("psNodeAffiliationsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeAffiliations, async=True) - host.bridge.addMethod("psNodeSubscriptionsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeSubscriptions, async=True) - host.bridge.addMethod("psNodeSubscriptionsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeSubscriptions, async=True) - host.bridge.addMethod("psNodeDelete", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True) - host.bridge.addMethod("psNodeWatchAdd", ".plugin", in_sign='sss', out_sign='', method=self._addWatch, async=False) - host.bridge.addMethod("psNodeWatchRemove", ".plugin", in_sign='sss', out_sign='', method=self._removeWatch, async=False) - host.bridge.addMethod("psAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getAffiliations, async=True) - host.bridge.addMethod("psItemsGet", ".plugin", in_sign='ssiassa{ss}s', out_sign='(asa{ss})', method=self._getItems, async=True) - host.bridge.addMethod("psItemSend", ".plugin", in_sign='ssssa{ss}s', out_sign='s', method=self._sendItem, async=True) - host.bridge.addMethod("psRetractItem", ".plugin", in_sign='sssbs', out_sign='', method=self._retractItem, async=True) - host.bridge.addMethod("psRetractItems", ".plugin", in_sign='ssasbs', out_sign='', method=self._retractItems, async=True) - host.bridge.addMethod("psSubscribe", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._subscribe, async=True) - host.bridge.addMethod("psUnsubscribe", ".plugin", in_sign='sss', out_sign='', method=self._unsubscribe, async=True) - host.bridge.addMethod("psSubscriptionsGet", ".plugin", in_sign='sss', out_sign='aa{ss}', method=self._subscriptions, async=True) - host.bridge.addMethod("psSubscribeToMany", ".plugin", in_sign='a(ss)sa{ss}s', out_sign='s', method=self._subscribeToMany) - host.bridge.addMethod("psGetSubscribeRTResult", ".plugin", in_sign='ss', out_sign='(ua(sss))', method=self._manySubscribeRTResult, async=True) - host.bridge.addMethod("psGetFromMany", ".plugin", in_sign='a(ss)ia{ss}s', out_sign='s', method=self._getFromMany) - host.bridge.addMethod("psGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssasa{ss}))', method=self._getFromManyRTResult, async=True) + host.bridge.addMethod( + "psNodeCreate", + ".plugin", + in_sign="ssa{ss}s", + out_sign="s", + method=self._createNode, + async=True, + ) + host.bridge.addMethod( + "psNodeConfigurationGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeConfiguration, + async=True, + ) + host.bridge.addMethod( + "psNodeConfigurationSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeConfiguration, + async=True, + ) + host.bridge.addMethod( + "psNodeAffiliationsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeAffiliations, + async=True, + ) + host.bridge.addMethod( + "psNodeAffiliationsSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeAffiliations, + async=True, + ) + host.bridge.addMethod( + "psNodeSubscriptionsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getNodeSubscriptions, + async=True, + ) + host.bridge.addMethod( + "psNodeSubscriptionsSet", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._setNodeSubscriptions, + async=True, + ) + host.bridge.addMethod( + "psNodeDelete", + ".plugin", + in_sign="sss", + out_sign="", + method=self._deleteNode, + async=True, + ) + host.bridge.addMethod( + "psNodeWatchAdd", + ".plugin", + in_sign="sss", + out_sign="", + method=self._addWatch, + async=False, + ) + host.bridge.addMethod( + "psNodeWatchRemove", + ".plugin", + in_sign="sss", + out_sign="", + method=self._removeWatch, + async=False, + ) + host.bridge.addMethod( + "psAffiliationsGet", + ".plugin", + in_sign="sss", + out_sign="a{ss}", + method=self._getAffiliations, + async=True, + ) + host.bridge.addMethod( + "psItemsGet", + ".plugin", + in_sign="ssiassa{ss}s", + out_sign="(asa{ss})", + method=self._getItems, + async=True, + ) + host.bridge.addMethod( + "psItemSend", + ".plugin", + in_sign="ssssa{ss}s", + out_sign="s", + method=self._sendItem, + async=True, + ) + host.bridge.addMethod( + "psRetractItem", + ".plugin", + in_sign="sssbs", + out_sign="", + method=self._retractItem, + async=True, + ) + host.bridge.addMethod( + "psRetractItems", + ".plugin", + in_sign="ssasbs", + out_sign="", + method=self._retractItems, + async=True, + ) + host.bridge.addMethod( + "psSubscribe", + ".plugin", + in_sign="ssa{ss}s", + out_sign="s", + method=self._subscribe, + async=True, + ) + host.bridge.addMethod( + "psUnsubscribe", + ".plugin", + in_sign="sss", + out_sign="", + method=self._unsubscribe, + async=True, + ) + host.bridge.addMethod( + "psSubscriptionsGet", + ".plugin", + in_sign="sss", + out_sign="aa{ss}", + method=self._subscriptions, + async=True, + ) + host.bridge.addMethod( + "psSubscribeToMany", + ".plugin", + in_sign="a(ss)sa{ss}s", + out_sign="s", + method=self._subscribeToMany, + ) + host.bridge.addMethod( + "psGetSubscribeRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sss))", + method=self._manySubscribeRTResult, + async=True, + ) + host.bridge.addMethod( + "psGetFromMany", + ".plugin", + in_sign="a(ss)ia{ss}s", + out_sign="s", + method=self._getFromMany, + ) + host.bridge.addMethod( + "psGetFromManyRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssasa{ss}))", + method=self._getFromManyRTResult, + async=True, + ) - # high level observer method - host.bridge.addSignal("psEvent", ".plugin", signature='ssssa{ss}s') # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile + # high level observer method + host.bridge.addSignal( + "psEvent", ".plugin", signature="ssssa{ss}s" + ) # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile # low level observer method, used if service/node is in watching list (see psNodeWatch* methods) - host.bridge.addSignal("psEventRaw", ".plugin", signature='sssass') # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile + host.bridge.addSignal( + "psEventRaw", ".plugin", signature="sssass" + ) # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile def getHandler(self, client): client.pubsub_client = SatPubSubClient(self.host, self) @@ -122,10 +280,18 @@ class XEP_0060(object): def profileConnected(self, client): client.pubsub_watching = set() try: - client.pubsub_service = jid.JID(self.host.memory.getConfig('', 'pubsub_service')) + client.pubsub_service = jid.JID( + self.host.memory.getConfig("", "pubsub_service") + ) except RuntimeError: - log.info(_(u"Can't retrieve pubsub_service from conf, we'll use first one that we find")) - client.pubsub_service = yield self.host.findServiceEntity(client, "pubsub", "service") + log.info( + _( + u"Can't retrieve pubsub_service from conf, we'll use first one that we find" + ) + ) + client.pubsub_service = yield self.host.findServiceEntity( + client, "pubsub", "service" + ) def getFeatures(self, profile): try: @@ -133,7 +299,11 @@ class XEP_0060(object): except exceptions.ProfileNotSetError: return {} try: - return {'service': client.pubsub_service.full() if client.pubsub_service is not None else ''} + return { + "service": client.pubsub_service.full() + if client.pubsub_service is not None + else "" + } except AttributeError: if self.host.isConnected(profile): log.debug("Profile is not connected, service is not checked yet") @@ -154,10 +324,10 @@ class XEP_0060(object): else: # rsm rsm_args = {} - for arg in ('max', 'after', 'before', 'index'): + for arg in ("max", "after", "before", "index"): try: - argname = "max_" if arg == 'max' else arg - rsm_args[argname] = extra.pop('rsm_{}'.format(arg)) + argname = "max_" if arg == "max" else arg + rsm_args[argname] = extra.pop("rsm_{}".format(arg)) except KeyError: continue @@ -168,16 +338,18 @@ class XEP_0060(object): # mam mam_args = {} - for arg in ('start', 'end'): + for arg in ("start", "end"): try: - mam_args[arg] = datetime.datetime.fromtimestamp(int(extra.pop('{}{}'.format(MAM_FILTER, arg))), tz.tzutc()) + mam_args[arg] = datetime.datetime.fromtimestamp( + int(extra.pop("{}{}".format(MAM_FILTER, arg))), tz.tzutc() + ) except (TypeError, ValueError): log.warning(u"Bad value for {} filter".format(arg)) except KeyError: continue try: - mam_args['with_jid'] = jid.JID(extra.pop('{}jid'.format(MAM_FILTER))) + mam_args["with_jid"] = jid.JID(extra.pop("{}jid".format(MAM_FILTER))) except (jid.InvalidFormat): log.warning(u"Bad value for jid filter") except KeyError: @@ -185,13 +357,13 @@ class XEP_0060(object): for name, value in extra.iteritems(): if name.startswith(MAM_FILTER): - var = name[len(MAM_FILTER):] - extra_fields = mam_args.setdefault('extra_fields', []) + var = name[len(MAM_FILTER) :] + extra_fields = mam_args.setdefault("extra_fields", []) extra_fields.append(data_form.Field(var=var, value=value)) if mam_args: - assert 'mam' not in extra - extra['mam'] = mam.MAMRequest(mam.buildForm(**mam_args)) + assert "mam" not in extra + extra["mam"] = mam.MAMRequest(mam.buildForm(**mam_args)) return Extra(rsm_request, extra) def addManagedNode(self, node, **kwargs): @@ -210,7 +382,7 @@ class XEP_0060(object): for event, cb in kwargs.iteritems(): event_name = event[:-3] assert event_name in C.PS_EVENTS - callbacks.setdefault(event_name,[]).append(cb) + callbacks.setdefault(event_name, []).append(cb) def removeManagedNode(self, node, *args): """Add a handler for a node @@ -231,14 +403,21 @@ class XEP_0060(object): except ValueError: pass else: - log.debug(u"removed callback {cb} for event {event} on node {node}".format( - cb=callback, event=event, node=node)) + log.debug( + u"removed callback {cb} for event {event} on node {node}".format( + cb=callback, event=event, node=node + ) + ) if not cb_list: del registred_cb[event] if not registred_cb: del self._node_cb[node] return - log.error(u"Trying to remove inexistant callback {cb} for node {node}".format(cb=callback, node=node)) + log.error( + u"Trying to remove inexistant callback {cb} for node {node}".format( + cb=callback, node=node + ) + ) # def listNodes(self, service, nodeIdentifier='', profile=C.PROF_KEY_NONE): # """Retrieve the name of the nodes that are accessible on the target service. @@ -270,11 +449,21 @@ class XEP_0060(object): # d.addCallback(lambda subs: [sub.getAttribute('node') for sub in subs if sub.getAttribute('subscription') == filter_]) # return d - def _sendItem(self, service, nodeIdentifier, payload, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE): + def _sendItem( + self, + service, + nodeIdentifier, + payload, + item_id=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) - d = self.sendItem(client, service, nodeIdentifier, payload, item_id or None, extra) - d.addCallback(lambda ret: ret or u'') + d = self.sendItem( + client, service, nodeIdentifier, payload, item_id or None, extra + ) + d.addCallback(lambda ret: ret or u"") return d def _getPublishedItemId(self, iq_elt, original_id): @@ -283,12 +472,14 @@ class XEP_0060(object): if not found original_id is returned, or empty string if it is None or empty string """ try: - item_id = iq_elt.pubsub.publish.item['id'] + item_id = iq_elt.pubsub.publish.item["id"] except (AttributeError, KeyError): item_id = None return item_id or original_id - def sendItem(self, client, service, nodeIdentifier, payload, item_id=None, extra=None): + def sendItem( + self, client, service, nodeIdentifier, payload, item_id=None, extra=None + ): """high level method to send one item @param service(jid.JID, None): service to send the item to @@ -305,21 +496,40 @@ class XEP_0060(object): return d def publish(self, client, service, nodeIdentifier, items=None): - return client.pubsub_client.publish(service, nodeIdentifier, items, client.pubsub_client.parent.jid) + return client.pubsub_client.publish( + service, nodeIdentifier, items, client.pubsub_client.parent.jid + ) def _unwrapMAMMessage(self, message_elt): try: - item_elt = (message_elt.elements(mam.NS_MAM, 'result').next() - .elements(C.NS_FORWARD, 'forwarded').next() - .elements(C.NS_CLIENT, 'message').next() - .elements('http://jabber.org/protocol/pubsub#event', 'event').next() - .elements('http://jabber.org/protocol/pubsub#event', 'items').next() - .elements('http://jabber.org/protocol/pubsub#event', 'item').next()) + item_elt = ( + message_elt.elements(mam.NS_MAM, "result") + .next() + .elements(C.NS_FORWARD, "forwarded") + .next() + .elements(C.NS_CLIENT, "message") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "event") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "items") + .next() + .elements("http://jabber.org/protocol/pubsub#event", "item") + .next() + ) except StopIteration: raise exceptions.DataError(u"Can't find Item in MAM message element") return item_elt - def _getItems(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _getItems( + self, + service="", + node="", + max_items=10, + item_ids=None, + sub_id=None, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """Get items from pubsub node @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @@ -328,11 +538,30 @@ class XEP_0060(object): service = jid.JID(service) if service else None max_items = None if max_items == C.NO_LIMIT else max_items extra = self.parseExtra(extra_dict) - d = self.getItems(client, service, node or None, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra) + d = self.getItems( + client, + service, + node or None, + max_items or None, + item_ids, + sub_id or None, + extra.rsm_request, + extra.extra, + ) d.addCallback(self.serItemsData) return d - def getItems(self, client, service, node, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None): + def getItems( + self, + client, + service, + node, + max_items=None, + item_ids=None, + sub_id=None, + rsm_request=None, + extra=None, + ): """Retrieve pubsub items from a node. @param service (JID, None): pubsub service. @@ -354,9 +583,11 @@ class XEP_0060(object): if extra is None: extra = {} try: - mam_query = extra['mam'] + mam_query = extra["mam"] except KeyError: - d = client.pubsub_client.items(service, node, max_items, item_ids, sub_id, None, rsm_request) + d = client.pubsub_client.items( + service, node, max_items, item_ids, sub_id, None, rsm_request + ) else: # if mam is requested, we have to do a totally different query if self._mam is None: @@ -368,25 +599,35 @@ class XEP_0060(object): if mam_query.node is None: mam_query.node = node elif mam_query.node != node: - raise exceptions.DataError(u"MAM query node is incoherent with getItems's node") + raise exceptions.DataError( + u"MAM query node is incoherent with getItems's node" + ) if mam_query.rsm is None: mam_query.rsm = rsm_request else: if mam_query.rsm != rsm_request: - raise exceptions.DataError(u"Conflict between RSM request and MAM's RSM request") + raise exceptions.DataError( + u"Conflict between RSM request and MAM's RSM request" + ) d = self._mam.getArchives(client, mam_query, service, self._unwrapMAMMessage) try: - subscribe = C.bool(extra['subscribe']) + subscribe = C.bool(extra["subscribe"]) except KeyError: subscribe = False def subscribeEb(failure, service, node): failure.trap(error.StanzaError) - log.warning(u"Could not subscribe to node {} on service {}: {}".format(node, unicode(service), unicode(failure.value))) + log.warning( + u"Could not subscribe to node {} on service {}: {}".format( + node, unicode(service), unicode(failure.value) + ) + ) def doSubscribe(items): - self.subscribe(service, node, profile_key=client.profile).addErrback(subscribeEb, service, node) + self.subscribe(service, node, profile_key=client.profile).addErrback( + subscribeEb, service, node + ) return items if subscribe: @@ -395,12 +636,18 @@ class XEP_0060(object): def addMetadata(result): items, rsm_response = result service_jid = service if service else client.jid.userhostJID() - metadata = {'service': service_jid, - 'node': node, - 'uri': self.getNodeURI(service_jid, node), - } + metadata = { + "service": service_jid, + "node": node, + "uri": self.getNodeURI(service_jid, node), + } if rsm_request is not None and rsm_response is not None: - metadata.update({'rsm_{}'.format(key): value for key, value in rsm_response.toDict().iteritems()}) + metadata.update( + { + "rsm_{}".format(key): value + for key, value in rsm_response.toDict().iteritems() + } + ) return (items, metadata) d.addCallback(addMetadata) @@ -431,17 +678,38 @@ class XEP_0060(object): # d_dict[publisher] = self.getItems(service, node, max_items, None, sub_id, rsm, client.profile) # defer.returnValue(d_dict) - def getOptions(self, service, nodeIdentifier, subscriber, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE): + def getOptions( + self, + service, + nodeIdentifier, + subscriber, + subscriptionIdentifier=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.getOptions(service, nodeIdentifier, subscriber, subscriptionIdentifier) + return client.pubsub_client.getOptions( + service, nodeIdentifier, subscriber, subscriptionIdentifier + ) - def setOptions(self, service, nodeIdentifier, subscriber, options, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE): + def setOptions( + self, + service, + nodeIdentifier, + subscriber, + options, + subscriptionIdentifier=None, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.setOptions(service, nodeIdentifier, subscriber, options, subscriptionIdentifier) + return client.pubsub_client.setOptions( + service, nodeIdentifier, subscriber, options, subscriptionIdentifier + ) def _createNode(self, service_s, nodeIdentifier, options, profile_key): client = self.host.getClient(profile_key) - return self.createNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options) + return self.createNode( + client, jid.JID(service_s) if service_s else None, nodeIdentifier, options + ) def createNode(self, client, service, nodeIdentifier=None, options=None): """Create a new node @@ -461,28 +729,31 @@ class XEP_0060(object): try: yield self.createNode(client, service, nodeIdentifier, options) except error.StanzaError as e: - if e.condition == 'conflict': + if e.condition == "conflict": pass else: raise e def _getNodeConfiguration(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier) + d = self.getConfiguration( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + def serialize(form): # FIXME: better more generic dataform serialisation should be available in SàT return {f.var: unicode(f.value) for f in form.fields.values()} + d.addCallback(serialize) return d def getConfiguration(self, client, service, nodeIdentifier): - request = pubsub.PubSubRequest('configureGet') + request = pubsub.PubSubRequest("configureGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq): - form = data_form.findForm(iq.pubsub.configure, - pubsub.NS_PUBSUB_NODE_CONFIG) + form = data_form.findForm(iq.pubsub.configure, pubsub.NS_PUBSUB_NODE_CONFIG) form.typeCheck() return form @@ -492,16 +763,19 @@ class XEP_0060(object): def _setNodeConfiguration(self, service_s, nodeIdentifier, options, profile_key): client = self.host.getClient(profile_key) - d = self.setConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options) + d = self.setConfiguration( + client, jid.JID(service_s) if service_s else None, nodeIdentifier, options + ) return d def setConfiguration(self, client, service, nodeIdentifier, options): - request = pubsub.PubSubRequest('configureSet') + request = pubsub.PubSubRequest("configureSet") request.recipient = service request.nodeIdentifier = nodeIdentifier - form = data_form.Form(formType='submit', - formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG) + form = data_form.Form( + formType="submit", formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG + ) form.makeFields(options) request.options = form @@ -510,7 +784,9 @@ class XEP_0060(object): def _getAffiliations(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier or None) + d = self.getAffiliations( + client, jid.JID(service_s) if service_s else None, nodeIdentifier or None + ) return d def getAffiliations(self, client, service, nodeIdentifier=None): @@ -519,19 +795,32 @@ class XEP_0060(object): @param nodeIdentifier(unicode, None): node to get affiliation from None to get all nodes affiliations for this service """ - request = pubsub.PubSubRequest('affiliations') + request = pubsub.PubSubRequest("affiliations") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'affiliations'))) + affiliations_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "affiliations")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing element: {}").format( + iq_elt.toXml + ) + ) try: - return {e['node']: e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB, 'affiliation'))} + return { + e["node"]: e["affiliation"] + for e in affiliations_elt.elements((pubsub.NS_PUBSUB, "affiliation")) + } except KeyError: - raise ValueError(_(u"Invalid result: bad element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) @@ -539,34 +828,62 @@ class XEP_0060(object): def _getNodeAffiliations(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier) - d.addCallback(lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()}) + d = self.getNodeAffiliations( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + d.addCallback( + lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()} + ) return d def getNodeAffiliations(self, client, service, nodeIdentifier): """Retrieve affiliations of a node owned by profile""" - request = pubsub.PubSubRequest('affiliationsGet') + request = pubsub.PubSubRequest("affiliationsGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, 'affiliations'))) + affiliations_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, "affiliations")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing element: {}").format( + iq_elt.toXml + ) + ) try: - return {jid.JID(e['jid']): e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB_OWNER, 'affiliation'))} + return { + jid.JID(e["jid"]): e["affiliation"] + for e in affiliations_elt.elements( + (pubsub.NS_PUBSUB_OWNER, "affiliation") + ) + } except KeyError: - raise ValueError(_(u"Invalid result: bad element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) return d - def _setNodeAffiliations(self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE): + def _setNodeAffiliations( + self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE + ): client = self.host.getClient(profile_key) - affiliations = {jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems()} - d = self.setNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier, affiliations) + affiliations = { + jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems() + } + d = self.setNodeAffiliations( + client, + jid.JID(service_s) if service_s else None, + nodeIdentifier, + affiliations, + ) return d def setNodeAffiliations(self, client, service, nodeIdentifier, affiliations): @@ -575,7 +892,7 @@ class XEP_0060(object): @param affiliations(dict[jid.JID, unicode]): affiliations to set check https://xmpp.org/extensions/xep-0060.html#affiliations for a list of possible affiliations """ - request = pubsub.PubSubRequest('affiliationsSet') + request = pubsub.PubSubRequest("affiliationsSet") request.recipient = service request.nodeIdentifier = nodeIdentifier request.affiliations = affiliations @@ -584,7 +901,9 @@ class XEP_0060(object): def _deleteNode(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - return self.deleteNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier) + return self.deleteNode( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) def deleteNode(self, client, service, nodeIdentifier): return client.pubsub_client.deleteNode(service, nodeIdentifier) @@ -607,48 +926,86 @@ class XEP_0060(object): service = jid.JID(service_s) if service_s else client.jid.userhostJID() client.pubsub_watching.remove((service, node)) - def _retractItem(self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key): - return self._retractItems(service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key) + def _retractItem( + self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key + ): + return self._retractItems( + service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key + ) - def _retractItems(self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key): - return self.retractItems(jid.JID(service_s) if service_s else None, nodeIdentifier, itemIdentifiers, notify, profile_key) + def _retractItems( + self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key + ): + return self.retractItems( + jid.JID(service_s) if service_s else None, + nodeIdentifier, + itemIdentifiers, + notify, + profile_key, + ) - def retractItems(self, service, nodeIdentifier, itemIdentifiers, notify=True, profile_key=C.PROF_KEY_NONE): + def retractItems( + self, + service, + nodeIdentifier, + itemIdentifiers, + notify=True, + profile_key=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile_key) - return client.pubsub_client.retractItems(service, nodeIdentifier, itemIdentifiers, notify=True) + return client.pubsub_client.retractItems( + service, nodeIdentifier, itemIdentifiers, notify=True + ) def _subscribe(self, service, nodeIdentifier, options, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) d = self.subscribe(client, service, nodeIdentifier, options=options or None) - d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u'') + d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u"") return d def subscribe(self, client, service, nodeIdentifier, sub_jid=None, options=None): # TODO: reimplement a subscribtion cache, checking that we have not subscription before trying to subscribe - return client.pubsub_client.subscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options) + return client.pubsub_client.subscribe( + service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options + ) def _unsubscribe(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) return self.unsubscribe(client, service, nodeIdentifier) - def unsubscribe(self, client, service, nodeIdentifier, sub_jid=None, subscriptionIdentifier=None, sender=None): - return client.pubsub_client.unsubscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), subscriptionIdentifier, sender) + def unsubscribe( + self, + client, + service, + nodeIdentifier, + sub_jid=None, + subscriptionIdentifier=None, + sender=None, + ): + return client.pubsub_client.unsubscribe( + service, + nodeIdentifier, + sub_jid or client.jid.userhostJID(), + subscriptionIdentifier, + sender, + ) - def _subscriptions(self, service, nodeIdentifier='', profile_key=C.PROF_KEY_NONE): + def _subscriptions(self, service, nodeIdentifier="", profile_key=C.PROF_KEY_NONE): client = self.host.getClient(profile_key) service = None if not service else jid.JID(service) def gotSubscriptions(subscriptions): # we replace pubsub.Subscription instance by dict that we can serialize for idx, sub in enumerate(subscriptions): - sub_dict = {'node': sub.nodeIdentifier, - 'subscriber': sub.subscriber.full(), - 'state': sub.state - } + sub_dict = { + "node": sub.nodeIdentifier, + "subscriber": sub.subscriber.full(), + "state": sub.state, + } if sub.subscriptionIdentifier is not None: - sub_dict['id'] = sub.subscriptionIdentifier + sub_dict["id"] = sub.subscriptionIdentifier subscriptions[idx] = sub_dict return subscriptions @@ -679,19 +1036,20 @@ class XEP_0060(object): # XXX: urllib.urlencode use "&" to separate value, while XMPP URL (cf. RFC 5122) # use ";" as a separator. So if more than one value is used in query_data, # urlencode MUST NOT BE USED. - query_data = [('node', node.encode('utf-8'))] + query_data = [("node", node.encode("utf-8"))] if item is not None: - query_data.append(('item', item.encode('utf-8'))) + query_data.append(("item", item.encode("utf-8"))) return "xmpp:{service}?;{query}".format( - service=service.userhost(), - query=urllib.urlencode(query_data) - ).decode('utf-8') + service=service.userhost(), query=urllib.urlencode(query_data) + ).decode("utf-8") ## methods to manage several stanzas/jids at once ## # generic # - def getRTResults(self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE): + def getRTResults( + self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE + ): return self.rt_sessions.getResults(session_id, on_success, on_error, profile) def serItemsData(self, items_data, item_cb=lambda item: item.toXml()): @@ -705,7 +1063,10 @@ class XEP_0060(object): @return (tuple): a serialised form ready to go throught bridge """ items, metadata = items_data - return [item_cb(item) for item in items], {key: unicode(value) for key, value in metadata.iteritems()} + return ( + [item_cb(item) for item in items], + {key: unicode(value) for key, value in metadata.iteritems()}, + ) def serItemsDataD(self, items_data, item_cb): """Helper method to serialise result from [getItems], deferred version @@ -719,11 +1080,20 @@ class XEP_0060(object): @return (tuple): a deferred which fire a serialised form ready to go throught bridge """ items, metadata = items_data + def eb(failure): - log.warning("Error while serialising/parsing item: {}".format(unicode(failure.value))) + log.warning( + "Error while serialising/parsing item: {}".format(unicode(failure.value)) + ) + d = defer.gatherResults([item_cb(item).addErrback(eb) for item in items]) + def finishSerialisation(serialised_items): - return [item for item in serialised_items if item is not None], {key: unicode(value) for key, value in metadata.iteritems()} + return ( + [item for item in serialised_items if item is not None], + {key: unicode(value) for key, value in metadata.iteritems()}, + ) + d.addCallback(finishSerialisation) return d @@ -739,14 +1109,23 @@ class XEP_0060(object): """ if failure_result is None: failure_result = () - return [('', result) if success else (unicode(result.result) or UNSPECIFIED, failure_result) for success, result in results] + return [ + ("", result) + if success + else (unicode(result.result) or UNSPECIFIED, failure_result) + for success, result in results + ] # subscribe # def _getNodeSubscriptions(self, service_s, nodeIdentifier, profile_key): client = self.host.getClient(profile_key) - d = self.getNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier) - d.addCallback(lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()}) + d = self.getNodeSubscriptions( + client, jid.JID(service_s) if service_s else None, nodeIdentifier + ) + d.addCallback( + lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()} + ) return d def getNodeSubscriptions(self, client, service, nodeIdentifier): @@ -756,30 +1135,55 @@ class XEP_0060(object): """ if not nodeIdentifier: raise exceptions.DataError("node identifier can't be empty") - request = pubsub.PubSubRequest('subscriptionsGet') + request = pubsub.PubSubRequest("subscriptionsGet") request.recipient = service request.nodeIdentifier = nodeIdentifier def cb(iq_elt): try: - subscriptions_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'subscriptions'))) + subscriptions_elt = next( + iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "subscriptions")) + ) except StopIteration: - raise ValueError(_(u"Invalid result: missing element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: missing element: {}").format( + iq_elt.toXml + ) + ) except AttributeError as e: raise ValueError(_(u"Invalid result: {}").format(e)) try: - return {jid.JID(s['jid']): s['subscription'] for s in subscriptions_elt.elements((pubsub.NS_PUBSUB, 'subscription'))} + return { + jid.JID(s["jid"]): s["subscription"] + for s in subscriptions_elt.elements( + (pubsub.NS_PUBSUB, "subscription") + ) + } except KeyError: - raise ValueError(_(u"Invalid result: bad element: {}").format(iq_elt.toXml)) + raise ValueError( + _(u"Invalid result: bad element: {}").format( + iq_elt.toXml + ) + ) d = request.send(client.xmlstream) d.addCallback(cb) return d - def _setNodeSubscriptions(self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE): + def _setNodeSubscriptions( + self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE + ): client = self.host.getClient(profile_key) - subscriptions = {jid.JID(jid_): subscription for jid_, subscription in subscriptions.iteritems()} - d = self.setNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier, subscriptions) + subscriptions = { + jid.JID(jid_): subscription + for jid_, subscription in subscriptions.iteritems() + } + d = self.setNodeSubscriptions( + client, + jid.JID(service_s) if service_s else None, + nodeIdentifier, + subscriptions, + ) return d def setNodeSubscriptions(self, client, service, nodeIdentifier, subscriptions): @@ -788,10 +1192,13 @@ class XEP_0060(object): @param subscriptions(dict[jid.JID, unicode]): subscriptions to set check https://xmpp.org/extensions/xep-0060.html#substates for a list of possible subscriptions """ - request = pubsub.PubSubRequest('subscriptionsSet') + request = pubsub.PubSubRequest("subscriptionsSet") request.recipient = service request.nodeIdentifier = nodeIdentifier - request.subscriptions = {pubsub.Subscription(nodeIdentifier, jid_, state) for jid_, state in subscriptions.iteritems()} + request.subscriptions = { + pubsub.Subscription(nodeIdentifier, jid_, state) + for jid_, state in subscriptions.iteritems() + } d = request.send(client.xmlstream) return d @@ -808,16 +1215,37 @@ class XEP_0060(object): @param profile_key: %(doc_profile_key)s """ profile = self.host.getClient(profile_key).profile - d = self.rt_sessions.getResults(session_id, on_success=lambda result:'', on_error=lambda failure:unicode(failure.value), profile=profile) + d = self.rt_sessions.getResults( + session_id, + on_success=lambda result: "", + on_error=lambda failure: unicode(failure.value), + profile=profile, + ) # we need to convert jid.JID to unicode with full() to serialise it for the bridge - d.addCallback(lambda ret: (ret[0], [(service.full(), node, '' if success else failure or UNSPECIFIED) - for (service, node), (success, failure) in ret[1].iteritems()])) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, "" if success else failure or UNSPECIFIED) + for (service, node), (success, failure) in ret[1].iteritems() + ], + ) + ) return d - def _subscribeToMany(self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE): - return self.subscribeToMany([(jid.JID(service), unicode(node)) for service, node in node_data], jid.JID(subscriber), options, profile_key) + def _subscribeToMany( + self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE + ): + return self.subscribeToMany( + [(jid.JID(service), unicode(node)) for service, node in node_data], + jid.JID(subscriber), + options, + profile_key, + ) - def subscribeToMany(self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE): + def subscribeToMany( + self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE + ): """Subscribe to several nodes at once. @param node_data (iterable[tuple]): iterable of tuple (service, node) where: @@ -831,7 +1259,9 @@ class XEP_0060(object): client = self.host.getClient(profile_key) deferreds = {} for service, node in node_data: - deferreds[(service, node)] = client.pubsub_client.subscribe(service, node, subscriber, options=options) + deferreds[(service, node)] = client.pubsub_client.subscribe( + service, node, subscriber, options=options + ) return self.rt_sessions.newSession(deferreds, client.profile) # found_nodes = yield self.listNodes(service, profile=client.profile) # subscribed_nodes = yield self.listSubscribedNodes(service, profile=client.profile) @@ -860,24 +1290,49 @@ class XEP_0060(object): - metadata(dict): serialised metadata """ profile = self.host.getClient(profile_key).profile - d = self.rt_sessions.getResults(session_id, - on_success=lambda result: ('', self.serItemsData(result)), - on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([],{})), - profile=profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d = self.rt_sessions.getResults( + session_id, + on_success=lambda result: ("", self.serItemsData(result)), + on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([], {})), + profile=profile, + ) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _getFromMany(self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _getFromMany( + self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE + ): """ @param max_item(int): maximum number of item to get, C.NO_LIMIT for no limit """ max_item = None if max_item == C.NO_LIMIT else max_item extra = self.parseExtra(extra_dict) - return self.getFromMany([(jid.JID(service), unicode(node)) for service, node in node_data], max_item, extra.rsm_request, extra.extra, profile_key) + return self.getFromMany( + [(jid.JID(service), unicode(node)) for service, node in node_data], + max_item, + extra.rsm_request, + extra.extra, + profile_key, + ) - def getFromMany(self, node_data, max_item=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): + def getFromMany( + self, + node_data, + max_item=None, + rsm_request=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): """Get items from many nodes at once @param node_data (iterable[tuple]): iterable of tuple (service, node) where: @@ -891,7 +1346,9 @@ class XEP_0060(object): client = self.host.getClient(profile_key) deferreds = {} for service, node in node_data: - deferreds[(service, node)] = self.getItems(client, service, node, max_item, rsm_request=rsm_request, extra=extra) + deferreds[(service, node)] = self.getItems( + client, service, node, max_item, rsm_request=rsm_request, extra=extra + ) return self.rt_sessions.newSession(deferreds, client.profile) @@ -930,7 +1387,13 @@ class SatPubSubClient(rsm.PubSubClient): client = self.parent if (event.sender, event.nodeIdentifier) in client.pubsub_watching: raw_items = [i.toXml() for i in event.items] - self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_ITEMS, raw_items, client.profile) + self.host.bridge.psEventRaw( + event.sender.full(), + event.nodeIdentifier, + C.PS_ITEMS, + raw_items, + client.profile, + ) def deleteReceived(self, event): log.debug((u"Publish node deleted")) @@ -938,7 +1401,9 @@ class SatPubSubClient(rsm.PubSubClient): callback(self.parent, event) client = self.parent if (event.sender, event.nodeIdentifier) in client.pubsub_watching: - self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile) + self.host.bridge.psEventRaw( + event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile + ) def subscriptions(self, service, nodeIdentifier, sender=None): """Return the list of subscriptions to the given service and node. @@ -949,7 +1414,7 @@ class SatPubSubClient(rsm.PubSubClient): @type nodeIdentifier: C{unicode} @return (list[pubsub.Subscription]): list of subscriptions """ - request = pubsub.PubSubRequest('subscriptions') + request = pubsub.PubSubRequest("subscriptions") request.recipient = service request.nodeIdentifier = nodeIdentifier request.sender = sender @@ -957,20 +1422,24 @@ class SatPubSubClient(rsm.PubSubClient): def cb(iq): subs = [] - for subscription_elt in iq.pubsub.subscriptions.elements(pubsub.NS_PUBSUB, 'subscription'): - subscription = pubsub.Subscription(subscription_elt['node'], - jid.JID(subscription_elt['jid']), - subscription_elt['subscription'], - subscriptionIdentifier=subscription_elt.getAttribute('subid')) + for subscription_elt in iq.pubsub.subscriptions.elements( + pubsub.NS_PUBSUB, "subscription" + ): + subscription = pubsub.Subscription( + subscription_elt["node"], + jid.JID(subscription_elt["jid"]), + subscription_elt["subscription"], + subscriptionIdentifier=subscription_elt.getAttribute("subid"), + ) subs.append(subscription) return subs return d.addCallback(cb) - def getDiscoInfo(self, requestor, service, nodeIdentifier=''): + def getDiscoInfo(self, requestor, service, nodeIdentifier=""): disco_info = [] self.host.trigger.point("PubSub Disco Info", disco_info, self.parent.profile) return disco_info - def getDiscoItems(self, requestor, service, nodeIdentifier=''): + def getDiscoItems(self, requestor, service, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0065.py b/sat/plugins/plugin_xep_0065.py index 719c860f..2181bf08 100755 --- a/sat/plugins/plugin_xep_0065.py +++ b/sat/plugins/plugin_xep_0065.py @@ -1,5 +1,5 @@ #!/usr/bin/env python2 -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # SAT plugin for managing xep-0065 @@ -56,6 +56,7 @@ from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.core import exceptions @@ -92,25 +93,27 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["NAT-PORT"], C.PI_MAIN: "XEP_0065", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams"""), } IQ_SET = '/iq[@type="set"]' -NS_BS = 'http://jabber.org/protocol/bytestreams' +NS_BS = "http://jabber.org/protocol/bytestreams" BS_REQUEST = IQ_SET + '/query[@xmlns="' + NS_BS + '"]' -TIMER_KEY = 'timer' -DEFER_KEY = 'finished' # key of the deferred used to track session end -SERVER_STARTING_PORT = 0 # starting number for server port search (0 to ask automatic attribution) +TIMER_KEY = "timer" +DEFER_KEY = "finished" # key of the deferred used to track session end +SERVER_STARTING_PORT = ( + 0 +) # starting number for server port search (0 to ask automatic attribution) # priorities are candidates local priorities, must be a int between 0 and 65535 PRIORITY_BEST_DIRECT = 10000 PRIORITY_DIRECT = 5000 PRIORITY_ASSISTED = 1000 -PRIORITY_PROXY = 0.2 # proxy is the last option for s5b -CANDIDATE_DELAY = 0.2 # see XEP-0260 §4 -CANDIDATE_DELAY_PROXY = 0.2 # additional time for proxy types (see XEP-0260 §4 note 3) +PRIORITY_PROXY = 0.2 # proxy is the last option for s5b +CANDIDATE_DELAY = 0.2 # see XEP-0260 §4 +CANDIDATE_DELAY_PROXY = 0.2 # additional time for proxy types (see XEP-0260 §4 note 3) -TIMEOUT = 300 # maxium time between session creation and stream start +TIMEOUT = 300 # maxium time between session creation and stream start # XXX: by default eveything is automatic # TODO: use these params to force use of specific proxy/port/IP @@ -132,14 +135,15 @@ TIMEOUT = 300 # maxium time between session creation and stream start # # """ -(STATE_INITIAL, -STATE_AUTH, -STATE_REQUEST, -STATE_READY, -STATE_AUTH_USERPASS, -STATE_CLIENT_INITIAL, -STATE_CLIENT_AUTH, -STATE_CLIENT_REQUEST, +( + STATE_INITIAL, + STATE_AUTH, + STATE_REQUEST, + STATE_READY, + STATE_AUTH_USERPASS, + STATE_CLIENT_INITIAL, + STATE_CLIENT_AUTH, + STATE_CLIENT_REQUEST, ) = xrange(8) SOCKS5_VER = 0x05 @@ -167,12 +171,21 @@ REPLY_CMD_NOT_SUPPORTED = 0x07 REPLY_ADDR_NOT_SUPPORTED = 0x08 -ProxyInfos = namedtuple("ProxyInfos", ['host', 'jid', 'port']) +ProxyInfos = namedtuple("ProxyInfos", ["host", "jid", "port"]) class Candidate(object): - - def __init__(self, host, port, type_, priority, jid_, id_=None, priority_local=False, factory=None): + def __init__( + self, + host, + port, + type_, + priority, + jid_, + id_=None, + priority_local=False, + factory=None, + ): """ @param host(unicode): host IP or domain @param port(int): port @@ -184,8 +197,7 @@ class Candidate(object): else priority is used as global one (and local priority is set to 0) """ assert isinstance(jid_, jid.JID) - self.host, self.port, self.type, self.jid = ( - host, int(port), type_, jid_) + self.host, self.port, self.type, self.jid = (host, int(port), type_, jid_) self.id = id_ if id_ is not None else unicode(uuid.uuid4()) if priority_local: self._local_priority = int(priority) @@ -204,7 +216,7 @@ class Candidate(object): try: self.factory.discard() except AttributeError: - pass # no discard for Socks5ServerFactory + pass # no discard for Socks5ServerFactory @property def local_priority(self): @@ -218,22 +230,25 @@ class Candidate(object): # similar to __unicode__ but we don't show jid and we encode id return "Candidate ({0.priority}): host={0.host} port={0.port} type={0.type}{id}".format( self, - id=u" id={}".format(self.id if self.id is not None else u'').encode('utf-8', 'ignore'), - ) + id=u" id={}".format(self.id if self.id is not None else u"").encode( + "utf-8", "ignore" + ), + ) def __unicode__(self): return u"Candidate ({0.priority}): host={0.host} port={0.port} jid={0.jid} type={0.type}{id}".format( - self, - id=u" id={}".format(self.id if self.id is not None else u''), - ) + self, id=u" id={}".format(self.id if self.id is not None else u"") + ) def __eq__(self, other): # self.id is is not used in __eq__ as the same candidate can have # different ids if proposed by initiator or responder try: - return (self.host == other.host and - self.port == other.port and - self.jid == other.jid) + return ( + self.host == other.host + and self.port == other.port + and self.jid == other.jid + ) except (AttributeError, TypeError): return False @@ -256,7 +271,7 @@ class Candidate(object): multiplier = 10 else: raise exceptions.InternalError(u"Unknown {} type !".format(self.type)) - return 2**16 * multiplier + self._local_priority + return 2 ** 16 * multiplier + self._local_priority def activate(self, sid, peer_jid, client): """Activate the proxy candidate @@ -269,15 +284,15 @@ class Candidate(object): """ assert self.type == XEP_0065.TYPE_PROXY iq_elt = client.IQ() - iq_elt['to'] = self.jid.full() - query_elt = iq_elt.addElement((NS_BS, 'query')) - query_elt['sid'] = sid - query_elt.addElement('activate', content=peer_jid.full()) + iq_elt["to"] = self.jid.full() + query_elt = iq_elt.addElement((NS_BS, "query")) + query_elt["sid"] = sid + query_elt.addElement("activate", content=peer_jid.full()) return iq_elt.send() def startTransfer(self, session_hash=None): if self.type == XEP_0065.TYPE_PROXY: - chunk_size = 4096 # Prosody's proxy reject bigger chunks by default + chunk_size = 4096 # Prosody's proxy reject bigger chunks by default else: chunk_size = None self.factory.startTransfer(session_hash, chunk_size=chunk_size) @@ -291,18 +306,20 @@ def getSessionHash(requester_jid, target_jid, sid): @param sid(unicode): session id @return (str): hash """ - return hashlib.sha1((sid + requester_jid.full() + target_jid.full()).encode('utf-8')).hexdigest() + return hashlib.sha1( + (sid + requester_jid.full() + target_jid.full()).encode("utf-8") + ).hexdigest() class SOCKSv5(protocol.Protocol): - CHUNK_SIZE = 2**16 + CHUNK_SIZE = 2 ** 16 def __init__(self, session_hash=None): """ @param session_hash(str): hash of the session must only be used in client mode """ - self.connection = defer.Deferred() # called when connection/auth is done + self.connection = defer.Deferred() # called when connection/auth is done if session_hash is not None: self.server_mode = False self._session_hash = session_hash @@ -318,13 +335,13 @@ class SOCKSv5(protocol.Protocol): self.addressType = 0 self.requestType = 0 self._stream_object = None - self.active = False # set to True when protocol is actually used for transfer - # used by factories to know when the finished Deferred can be triggered + self.active = False # set to True when protocol is actually used for transfer + # used by factories to know when the finished Deferred can be triggered @property def stream_object(self): if self._stream_object is None: - self._stream_object = self.getSession()['stream_object'] + self._stream_object = self.getSession()["stream_object"] if self.server_mode: self._stream_object.registerProducer(self.transport, True) return self._stream_object @@ -342,22 +359,22 @@ class SOCKSv5(protocol.Protocol): def _startNegotiation(self): log.debug("starting negotiation (client mode)") self.state = STATE_CLIENT_AUTH - self.transport.write(struct.pack('!3B', SOCKS5_VER, 1, AUTHMECH_ANON)) + self.transport.write(struct.pack("!3B", SOCKS5_VER, 1, AUTHMECH_ANON)) def _parseNegotiation(self): try: # Parse out data - ver, nmethod = struct.unpack('!BB', self.buf[:2]) - methods = struct.unpack('%dB' % nmethod, self.buf[2:nmethod + 2]) + ver, nmethod = struct.unpack("!BB", self.buf[:2]) + methods = struct.unpack("%dB" % nmethod, self.buf[2 : nmethod + 2]) # Ensure version is correct if ver != 5: - self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID)) self.transport.loseConnection() return # Trim off front of the buffer - self.buf = self.buf[nmethod + 2:] + self.buf = self.buf[nmethod + 2 :] # Check for supported auth mechs for m in self.supportedAuthMechs: @@ -368,12 +385,12 @@ class SOCKSv5(protocol.Protocol): elif m == AUTHMECH_USERPASS: self.state = STATE_AUTH_USERPASS # Complete negotiation w/ this method - self.transport.write(struct.pack('!BB', SOCKS5_VER, m)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, m)) return # No supported mechs found, notify client and close the connection log.warning(u"Unsupported authentication mechanism") - self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID)) self.transport.loseConnection() except struct.error: pass @@ -381,34 +398,34 @@ class SOCKSv5(protocol.Protocol): def _parseUserPass(self): try: # Parse out data - ver, ulen = struct.unpack('BB', self.buf[:2]) - uname, = struct.unpack('%ds' % ulen, self.buf[2:ulen + 2]) - plen, = struct.unpack('B', self.buf[ulen + 2]) - password, = struct.unpack('%ds' % plen, self.buf[ulen + 3:ulen + 3 + plen]) + ver, ulen = struct.unpack("BB", self.buf[:2]) + uname, = struct.unpack("%ds" % ulen, self.buf[2 : ulen + 2]) + plen, = struct.unpack("B", self.buf[ulen + 2]) + password, = struct.unpack("%ds" % plen, self.buf[ulen + 3 : ulen + 3 + plen]) # Trim off fron of the buffer - self.buf = self.buf[3 + ulen + plen:] + self.buf = self.buf[3 + ulen + plen :] # Fire event to authenticate user if self.authenticateUserPass(uname, password): # Signal success self.state = STATE_REQUEST - self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x00)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x00)) else: # Signal failure - self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x01)) + self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x01)) self.transport.loseConnection() except struct.error: pass def sendErrorReply(self, errorcode): # Any other address types are not supported - result = struct.pack('!BBBBIH', SOCKS5_VER, errorcode, 0, 1, 0, 0) + result = struct.pack("!BBBBIH", SOCKS5_VER, errorcode, 0, 1, 0, 0) self.transport.write(result) self.transport.loseConnection() def _parseRequest(self): try: # Parse out data and trim buffer accordingly - ver, cmd, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4]) + ver, cmd, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4]) # Ensure we actually support the requested address type if self.addressType not in self.supportedAddrs: @@ -417,12 +434,12 @@ class SOCKSv5(protocol.Protocol): # Deal with addresses if self.addressType == ADDR_IPV4: - addr, port = struct.unpack('!IH', self.buf[4:10]) + addr, port = struct.unpack("!IH", self.buf[4:10]) self.buf = self.buf[10:] elif self.addressType == ADDR_DOMAINNAME: nlen = ord(self.buf[4]) - addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:]) - self.buf = self.buf[7 + len(addr):] + addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:]) + self.buf = self.buf[7 + len(addr) :] else: # Any other address types are not supported self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -449,13 +466,22 @@ class SOCKSv5(protocol.Protocol): def _makeRequest(self): hash_ = self._session_hash - request = struct.pack('!5B%dsH' % len(hash_), SOCKS5_VER, CMD_CONNECT, 0, ADDR_DOMAINNAME, len(hash_), hash_, 0) + request = struct.pack( + "!5B%dsH" % len(hash_), + SOCKS5_VER, + CMD_CONNECT, + 0, + ADDR_DOMAINNAME, + len(hash_), + hash_, + 0, + ) self.transport.write(request) self.state = STATE_CLIENT_REQUEST def _parseRequestReply(self): try: - ver, rep, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4]) + ver, rep, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4]) # Ensure we actually support the requested address type if self.addressType not in self.supportedAddrs: self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -463,12 +489,12 @@ class SOCKSv5(protocol.Protocol): # Deal with addresses if self.addressType == ADDR_IPV4: - addr, port = struct.unpack('!IH', self.buf[4:10]) + addr, port = struct.unpack("!IH", self.buf[4:10]) self.buf = self.buf[10:] elif self.addressType == ADDR_DOMAINNAME: nlen = ord(self.buf[4]) - addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:]) - self.buf = self.buf[7 + len(addr):] + addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:]) + self.buf = self.buf[7 + len(addr) :] else: # Any other address types are not supported self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED) @@ -487,7 +513,11 @@ class SOCKSv5(protocol.Protocol): return None def connectionMade(self): - log.debug(u"Socks5 connectionMade (mode = {})".format("server" if self.state == STATE_INITIAL else "client")) + log.debug( + u"Socks5 connectionMade (mode = {})".format( + "server" if self.state == STATE_INITIAL else "client" + ) + ) if self.state == STATE_CLIENT_INITIAL: self._startNegotiation() @@ -495,8 +525,11 @@ class SOCKSv5(protocol.Protocol): # Check that this session is expected if not self.factory.addToSession(addr, self): self.sendErrorReply(REPLY_CONN_REFUSED) - log.warning(u"Unexpected connection request received from {host}" - .format(host=self.transport.getPeer().host)) + log.warning( + u"Unexpected connection request received from {host}".format( + host=self.transport.getPeer().host + ) + ) return self._session_hash = addr self.connectCompleted(addr, 0) @@ -519,10 +552,20 @@ class SOCKSv5(protocol.Protocol): def connectCompleted(self, remotehost, remoteport): if self.addressType == ADDR_IPV4: - result = struct.pack('!BBBBIH', SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport) + result = struct.pack( + "!BBBBIH", SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport + ) elif self.addressType == ADDR_DOMAINNAME: - result = struct.pack('!BBBBB%dsH' % len(remotehost), SOCKS5_VER, REPLY_SUCCESS, 0, - ADDR_DOMAINNAME, len(remotehost), remotehost, remoteport) + result = struct.pack( + "!BBBBB%dsH" % len(remotehost), + SOCKS5_VER, + REPLY_SUCCESS, + 0, + ADDR_DOMAINNAME, + len(remotehost), + remotehost, + remoteport, + ) self.transport.write(result) self.state = STATE_READY @@ -553,7 +596,7 @@ class SOCKSv5(protocol.Protocol): if self.state == STATE_CLIENT_REQUEST: self._parseRequestReply() if self.state == STATE_CLIENT_AUTH: - ver, method = struct.unpack('!BB', buf) + ver, method = struct.unpack("!BB", buf) self.buf = self.buf[2:] if ver != SOCKS5_VER or method != AUTHMECH_ANON: self.transport.loseConnection() @@ -564,7 +607,7 @@ class SOCKSv5(protocol.Protocol): log.debug(u"Socks5 connection lost: {}".format(reason.value)) if self.state != STATE_READY: self.connection.errback(reason) - if self.server_mode : + if self.server_mode: self.factory.removeFromSession(self._session_hash, self, reason) @@ -583,7 +626,7 @@ class Socks5ServerFactory(protocol.ServerFactory): def startTransfer(self, session_hash, chunk_size=None): session = self.getSession(session_hash) try: - protocol = session['protocols'][0] + protocol = session["protocols"][0] except (KeyError, IndexError): log.error(u"Can't start file transfer, can't find protocol") else: @@ -603,7 +646,7 @@ class Socks5ServerFactory(protocol.ServerFactory): except KeyError: return False else: - session_data.setdefault('protocols', []).append(protocol) + session_data.setdefault("protocols", []).append(protocol) return True def removeFromSession(self, session_hash, protocol, reason): @@ -616,7 +659,7 @@ class Socks5ServerFactory(protocol.ServerFactory): @param reason(failure.Failure): reason of the removal """ try: - protocols = self.getSession(session_hash)['protocols'] + protocols = self.getSession(session_hash)["protocols"] protocols.remove(protocol) except (KeyError, ValueError): log.error(u"Protocol not found in session while it should be there") @@ -686,10 +729,10 @@ class Socks5ClientFactory(protocol.ClientFactory): class XEP_0065(object): NAMESPACE = NS_BS - TYPE_DIRECT = 'direct' - TYPE_ASSISTED = 'assisted' - TYPE_TUNEL = 'tunel' - TYPE_PROXY = 'proxy' + TYPE_DIRECT = "direct" + TYPE_ASSISTED = "assisted" + TYPE_TUNEL = "tunel" + TYPE_PROXY = "proxy" Candidate = Candidate def __init__(self, host): @@ -698,16 +741,16 @@ class XEP_0065(object): # session data self.hash_clients_map = {} # key: hash of the transfer session, value: session data - self._cache_proxies = {} # key: server jid, value: proxy data + self._cache_proxies = {} # key: server jid, value: proxy data # misc data self._server_factory = None self._external_port = None # plugins shortcuts - self._ip = self.host.plugins['IP'] + self._ip = self.host.plugins["IP"] try: - self._np = self.host.plugins['NAT-PORT'] + self._np = self.host.plugins["NAT-PORT"] except KeyError: log.debug(u"NAT Port plugin not available") self._np = None @@ -739,16 +782,22 @@ class XEP_0065(object): try: listening_port = reactor.listenTCP(port, self._server_factory) except internet_error.CannotListenError as e: - log.debug(u"Cannot listen on port {port}: {err_msg}{err_num}".format( - port=port, - err_msg=e.socketError.strerror, - err_num=u' (error code: {})'.format(e.socketError.errno), - )) + log.debug( + u"Cannot listen on port {port}: {err_msg}{err_num}".format( + port=port, + err_msg=e.socketError.strerror, + err_num=u" (error code: {})".format(e.socketError.errno), + ) + ) else: self._server_factory_port = listening_port.getHost().port break - log.info(_("Socks5 Stream server launched on port {}").format(self._server_factory_port)) + log.info( + _("Socks5 Stream server launched on port {}").format( + self._server_factory_port + ) + ) return self._server_factory @defer.inlineCallbacks @@ -759,36 +808,43 @@ class XEP_0065(object): @return ((D)(ProxyInfos, None)): Found proxy infos, or None if not acceptable proxy is found """ + def notFound(server): log.info(u"No proxy found on this server") self._cache_proxies[server] = None defer.returnValue(None) + server = client.jid.host try: defer.returnValue(self._cache_proxies[server]) except KeyError: pass try: - proxy = (yield self.host.findServiceEntities(client, 'proxy', 'bytestreams')).pop() + proxy = ( + yield self.host.findServiceEntities(client, "proxy", "bytestreams") + ).pop() except (defer.CancelledError, StopIteration, KeyError): notFound(server) - iq_elt = client.IQ('get') - iq_elt['to'] = proxy.full() - iq_elt.addElement((NS_BS, 'query')) + iq_elt = client.IQ("get") + iq_elt["to"] = proxy.full() + iq_elt.addElement((NS_BS, "query")) try: result_elt = yield iq_elt.send() except jabber_error.StanzaError as failure: - log.warning(u"Error while requesting proxy info on {jid}: {error}" - .format(proxy.full(), failure)) + log.warning( + u"Error while requesting proxy info on {jid}: {error}".format( + proxy.full(), failure + ) + ) notFound(server) try: - query_elt = result_elt.elements(NS_BS, 'query').next() - streamhost_elt = query_elt.elements(NS_BS, 'streamhost').next() - host = streamhost_elt['host'] - jid_ = streamhost_elt['jid'] - port = streamhost_elt['port'] + query_elt = result_elt.elements(NS_BS, "query").next() + streamhost_elt = query_elt.elements(NS_BS, "streamhost").next() + host = streamhost_elt["host"] + jid_ = streamhost_elt["jid"] + port = streamhost_elt["port"] if not all((host, jid, port)): raise KeyError jid_ = jid.JID(jid_) @@ -818,7 +874,9 @@ class XEP_0065(object): if self._np is None: log.warning(u"NAT port plugin not available, we can't map port") else: - ext_port = yield self._np.mapPort(local_port, desc=u"SaT socks5 stream") + ext_port = yield self._np.mapPort( + local_port, desc=u"SaT socks5 stream" + ) if ext_port is None: log.warning(u"Can't map NAT port") else: @@ -843,17 +901,56 @@ class XEP_0065(object): # the preferred direct connection ip = local_ips.pop(0) - candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_BEST_DIRECT, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + ip, + local_port, + XEP_0065.TYPE_DIRECT, + PRIORITY_BEST_DIRECT, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) for ip in local_ips: - candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_DIRECT, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + ip, + local_port, + XEP_0065.TYPE_DIRECT, + PRIORITY_DIRECT, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) # then the assisted one if ext_port is not None: - candidates.append(Candidate(external_ip, ext_port, XEP_0065.TYPE_ASSISTED, PRIORITY_ASSISTED, client.jid, priority_local=True, factory=server_factory)) + candidates.append( + Candidate( + external_ip, + ext_port, + XEP_0065.TYPE_ASSISTED, + PRIORITY_ASSISTED, + client.jid, + priority_local=True, + factory=server_factory, + ) + ) # finally the proxy if proxy: - candidates.append(Candidate(proxy.host, proxy.port, XEP_0065.TYPE_PROXY, PRIORITY_PROXY, proxy.jid, priority_local=True)) + candidates.append( + Candidate( + proxy.host, + proxy.port, + XEP_0065.TYPE_PROXY, + PRIORITY_PROXY, + proxy.jid, + priority_local=True, + ) + ) # should be already sorted, but just in case the priorities get weird candidates.sort(key=lambda c: c.priority, reverse=True) @@ -870,7 +967,9 @@ class XEP_0065(object): candidate.factory.connector = connector return candidate.factory.connection - def connectCandidate(self, client, candidate, session_hash, peer_session_hash=None, delay=None): + def connectCandidate( + self, client, candidate, session_hash, peer_session_hash=None, delay=None + ): """Connect to a candidate Connection will be done with a Socks5ClientFactory @@ -900,16 +999,30 @@ class XEP_0065(object): d.addCallback(self._addConnector, candidate) return d - def tryCandidates(self, client, candidates, session_hash, peer_session_hash, connection_cb=None, connection_eb=None): + def tryCandidates( + self, + client, + candidates, + session_hash, + peer_session_hash, + connection_cb=None, + connection_eb=None, + ): defers_list = [] for candidate in candidates: delay = CANDIDATE_DELAY * len(defers_list) if candidate.type == XEP_0065.TYPE_PROXY: delay += CANDIDATE_DELAY_PROXY - d = self.connectCandidate(client, candidate, session_hash, peer_session_hash, delay) + d = self.connectCandidate( + client, candidate, session_hash, peer_session_hash, delay + ) if connection_cb is not None: - d.addCallback(lambda dummy, candidate=candidate, client=client: connection_cb(client, candidate)) + d.addCallback( + lambda dummy, candidate=candidate, client=client: connection_cb( + client, candidate + ) + ) if connection_eb is not None: d.addErrback(connection_eb, client, candidate) defers_list.append(d) @@ -942,9 +1055,11 @@ class XEP_0065(object): if failure.check(defer.CancelledError): log.debug(u"Connection of {} has been cancelled".format(candidate)) else: - log.info(u"Connection of {candidate} Failed: {error}".format( - candidate = candidate, - error = failure.value)) + log.info( + u"Connection of {candidate} Failed: {error}".format( + candidate=candidate, error=failure.value + ) + ) candidates[candidates.index(candidate)] = None def allTested(self): @@ -952,7 +1067,14 @@ class XEP_0065(object): good_candidates = [c for c in candidates if c] return good_candidates[0] if good_candidates else None - defer_candidates = self.tryCandidates(client, candidates, session_hash, peer_session_hash, connectionCb, connectionEb) + defer_candidates = self.tryCandidates( + client, + candidates, + session_hash, + peer_session_hash, + connectionCb, + connectionEb, + ) d_list = defer.DeferredList(defer_candidates) d_list.addCallback(allTested) return d_list @@ -977,11 +1099,13 @@ class XEP_0065(object): @param failure_(None, failure.Failure): None if eveything was fine, a failure else @return (None, failure.Failure): failure_ is returned """ - log.debug(u'Cleaning session with hash {hash}{id}: {reason}'.format( - hash=session_hash, - reason='' if failure_ is None else failure_.value, - id='' if sid is None else u' (id: {})'.format(sid), - )) + log.debug( + u"Cleaning session with hash {hash}{id}: {reason}".format( + hash=session_hash, + reason="" if failure_ is None else failure_.value, + id="" if sid is None else u" (id: {})".format(sid), + ) + ) try: assert self.hash_clients_map[session_hash] == client @@ -1004,7 +1128,7 @@ class XEP_0065(object): del client._s5b_sessions[session_hash] try: - session_data['timer'].cancel() + session_data["timer"].cancel() except (internet_error.AlreadyCalled, internet_error.AlreadyCancelled): pass @@ -1025,19 +1149,19 @@ class XEP_0065(object): session_data[client] = client def gotCandidates(candidates): - session_data['candidates'] = candidates + session_data["candidates"] = candidates iq_elt = client.IQ() iq_elt["from"] = client.jid.full() iq_elt["to"] = to_jid.full() - query_elt = iq_elt.addElement((NS_BS, 'query')) - query_elt['mode'] = 'tcp' - query_elt['sid'] = sid + query_elt = iq_elt.addElement((NS_BS, "query")) + query_elt["mode"] = "tcp" + query_elt["sid"] = sid for candidate in candidates: - streamhost = query_elt.addElement('streamhost') - streamhost['host'] = candidate.host - streamhost['port'] = str(candidate.port) - streamhost['jid'] = candidate.jid.full() + streamhost = query_elt.addElement("streamhost") + streamhost["host"] = candidate.host + streamhost["port"] = str(candidate.port) + streamhost["jid"] = candidate.jid.full() log.debug(u"Candidate proposed: {}".format(candidate)) d = iq_elt.send() @@ -1055,31 +1179,39 @@ class XEP_0065(object): @param iq_elt(domish.Element): result """ try: - query_elt = iq_elt.elements(NS_BS, 'query').next() - streamhost_used_elt = query_elt.elements(NS_BS, 'streamhost-used').next() + query_elt = iq_elt.elements(NS_BS, "query").next() + streamhost_used_elt = query_elt.elements(NS_BS, "streamhost-used").next() except StopIteration: log.warning(u"No streamhost found in stream query") # FIXME: must clean session return - streamhost_jid = jid.JID(streamhost_used_elt['jid']) + streamhost_jid = jid.JID(streamhost_used_elt["jid"]) try: - candidate = (c for c in session_data['candidates'] if c.jid == streamhost_jid).next() + candidate = ( + c for c in session_data["candidates"] if c.jid == streamhost_jid + ).next() except StopIteration: - log.warning(u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full())) + log.warning( + u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full()) + ) return else: log.info(u"Candidate choosed by target: {}".format(candidate)) if candidate.type == XEP_0065.TYPE_PROXY: log.info(u"A Socks5 proxy is used") - d = self.connectCandidate(client, candidate, session_data['hash']) - d.addCallback(lambda dummy: candidate.activate(session_data['id'], session_data['peer_jid'], client)) + d = self.connectCandidate(client, candidate, session_data["hash"]) + d.addCallback( + lambda dummy: candidate.activate( + session_data["id"], session_data["peer_jid"], client + ) + ) d.addErrback(self._activationEb) else: d = defer.succeed(None) - d.addCallback(lambda dummy: candidate.startTransfer(session_data['hash'])) + d.addCallback(lambda dummy: candidate.startTransfer(session_data["hash"])) def _activationEb(self, failure): log.warning(u"Proxy activation error: {}".format(failure.value)) @@ -1105,7 +1237,7 @@ class XEP_0065(object): @return (dict): session data """ if sid in client.xep_0065_sid_session: - raise exceptions.ConflictError(u'A session with this id already exists !') + raise exceptions.ConflictError(u"A session with this id already exists !") if requester: session_hash = getSessionHash(client.jid, to_jid, sid) session_data = self._registerHash(client, session_hash, stream_object) @@ -1115,15 +1247,19 @@ class XEP_0065(object): session_d.addBoth(self.killSession, session_hash, sid, client) session_data = client._s5b_sessions[session_hash] = { DEFER_KEY: session_d, - TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client), - } + TIMER_KEY: reactor.callLater( + TIMEOUT, self._timeOut, session_hash, client + ), + } client.xep_0065_sid_session[sid] = session_data session_data.update( - {'id': sid, - 'peer_jid': to_jid, - 'stream_object': stream_object, - 'hash': session_hash, - }) + { + "id": sid, + "peer_jid": to_jid, + "stream_object": stream_object, + "hash": session_hash, + } + ) return session_data @@ -1141,7 +1277,7 @@ class XEP_0065(object): """ if client is None: try: - client = self.hash_clients_map[session_hash] + client = self.hash_clients_map[session_hash] except KeyError as e: log.warning(u"The requested session doesn't exists !") raise e @@ -1167,10 +1303,10 @@ class XEP_0065(object): session_data = client._s5b_sessions[session_hash] = { DEFER_KEY: session_d, TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client), - } + } if stream_object is not None: - session_data['stream_object'] = stream_object + session_data["stream_object"] = stream_object assert session_hash not in self.hash_clients_map self.hash_clients_map[session_hash] = client @@ -1180,22 +1316,22 @@ class XEP_0065(object): def associateStreamObject(self, client, session_hash, stream_object): """Associate a stream object with a session""" session_data = self.getSession(client, session_hash) - assert 'stream_object' not in session_data - session_data['stream_object'] = stream_object + assert "stream_object" not in session_data + session_data["stream_object"] = stream_object def streamQuery(self, iq_elt, client): log.debug(u"BS stream query") iq_elt.handled = True - query_elt = iq_elt.elements(NS_BS, 'query').next() + query_elt = iq_elt.elements(NS_BS, "query").next() try: - sid = query_elt['sid'] + sid = query_elt["sid"] except KeyError: log.warning(u"Invalid bystreams request received") return client.sendError(iq_elt, "bad-request") - streamhost_elts = list(query_elt.elements(NS_BS, 'streamhost')) + streamhost_elts = list(query_elt.elements(NS_BS, "streamhost")) if not streamhost_elts: return client.sendError(iq_elt, "bad-request") @@ -1203,7 +1339,7 @@ class XEP_0065(object): session_data = client.xep_0065_sid_session[sid] except KeyError: log.warning(u"Ignoring unexpected BS transfer: {}".format(sid)) - return client.sendError(iq_elt, 'not-acceptable') + return client.sendError(iq_elt, "not-acceptable") peer_jid = session_data["peer_jid"] = jid.JID(iq_elt["from"]) @@ -1211,7 +1347,7 @@ class XEP_0065(object): nb_sh = len(streamhost_elts) for idx, sh_elt in enumerate(streamhost_elts): try: - host, port, jid_ = sh_elt['host'], sh_elt['port'], jid.JID(sh_elt['jid']) + host, port, jid_ = sh_elt["host"], sh_elt["port"], jid.JID(sh_elt["jid"]) except KeyError: log.warning(u"malformed streamhost element") return client.sendError(iq_elt, "bad-request") @@ -1225,19 +1361,19 @@ class XEP_0065(object): for candidate in candidates: log.info(u"Candidate proposed: {}".format(candidate)) - d = self.getBestCandidate(client, candidates, session_data['hash']) + d = self.getBestCandidate(client, candidates, session_data["hash"]) d.addCallback(self._ackStream, iq_elt, session_data, client) def _ackStream(self, candidate, iq_elt, session_data, client): if candidate is None: log.info("No streamhost candidate worked, we have to end negotiation") - return client.sendError(iq_elt, 'item-not-found') + return client.sendError(iq_elt, "item-not-found") log.info(u"We choose: {}".format(candidate)) - result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = result_elt.addElement((NS_BS, 'query')) - query_elt['sid'] = session_data['id'] - streamhost_used_elt = query_elt.addElement('streamhost-used') - streamhost_used_elt['jid'] = candidate.jid.full() + result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = result_elt.addElement((NS_BS, "query")) + query_elt["sid"] = session_data["id"] + streamhost_used_elt = query_elt.addElement("streamhost-used") + streamhost_used_elt["jid"] = candidate.jid.full() client.send(result_elt) @@ -1249,10 +1385,12 @@ class XEP_0065_handler(XMPPHandler): self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent) + self.xmlstream.addObserver( + BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_BS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0070.py b/sat/plugins/plugin_xep_0070.py index 1e626c81..0f017fe9 100644 --- a/sat/plugins/plugin_xep_0070.py +++ b/sat/plugins/plugin_xep_0070.py @@ -21,25 +21,27 @@ from sat.core.constants import Const as C from sat.core.log import getLogger from twisted.words.protocols.jabber import xmlstream from twisted.words.protocols import jabber + log = getLogger(__name__) from sat.tools import xml_tools from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth' +NS_HTTP_AUTH = "http://jabber.org/protocol/http-auth" -IQ = 'iq' -IQ_GET = '/'+IQ+'[@type="get"]' +IQ = "iq" +IQ_GET = "/" + IQ + '[@type="get"]' IQ_HTTP_AUTH_REQUEST = IQ_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' -MSG = 'message' -MSG_GET = '/'+MSG+'[@type="normal"]' +MSG = "message" +MSG_GET = "/" + MSG + '[@type="normal"]' MSG_HTTP_AUTH_REQUEST = MSG_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]' @@ -51,7 +53,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0070", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP""") + C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP"""), } @@ -88,16 +90,20 @@ class XEP_0070(object): def _treatHttpAuthRequest(self, elt, stanzaType, client): elt.handled = True - auth_elt = elt.elements(NS_HTTP_AUTH, 'confirm').next() - auth_id = auth_elt['id'] - auth_method = auth_elt['method'] - auth_url = auth_elt['url'] + auth_elt = elt.elements(NS_HTTP_AUTH, "confirm").next() + auth_id = auth_elt["id"] + auth_method = auth_elt["method"] + auth_url = auth_elt["url"] self._dictRequest[client] = (auth_id, auth_method, auth_url, stanzaType, elt) - confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id='') - confirm_ui.addText(D_(u"{} needs to validate your identity, do you agree ?".format(auth_url))) + confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id="") + confirm_ui.addText( + D_(u"{} needs to validate your identity, do you agree ?".format(auth_url)) + ) confirm_ui.addText(D_(u"Validation code : {}".format(auth_id))) - confirm_ui.addText(D_(u"Please check that this code is the same as on {}".format(auth_url))) + confirm_ui.addText( + D_(u"Please check that this code is the same as on {}".format(auth_url)) + ) confirm_ui.addText(u"") confirm_ui.addText(D_(u"Submit to authorize, cancel otherwise.")) d = xml_tools.deferredUI(self.host, confirm_ui, chained=False) @@ -107,7 +113,7 @@ class XEP_0070(object): def _authRequestCallback(self, result, profile): client = self.host.getClient(profile) try: - cancelled = result['cancelled'] + cancelled = result["cancelled"] except KeyError: cancelled = False @@ -119,23 +125,25 @@ class XEP_0070(object): authorized = False else: try: - auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[client] + auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[ + client + ] del self._dictRequest[client] authorized = True except KeyError: authorized = False if authorized: - if (stanzaType == IQ): + if stanzaType == IQ: # iq log.debug(_(u"XEP-0070 reply iq")) - iq_result_elt = xmlstream.toResponse(elt, 'result') + iq_result_elt = xmlstream.toResponse(elt, "result") client.send(iq_result_elt) - elif (stanzaType == MSG): + elif stanzaType == MSG: # message log.debug(_(u"XEP-0070 reply message")) - msg_result_elt = xmlstream.toResponse(elt, 'result') - msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, 'confirm').next()) + msg_result_elt = xmlstream.toResponse(elt, "result") + msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, "confirm").next()) client.send(msg_result_elt) else: log.debug(_(u"XEP-0070 reply error")) @@ -152,11 +160,19 @@ class XEP_0070_handler(XMPPHandler): self.profile = profile def connectionInitialized(self): - self.xmlstream.addObserver(IQ_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestIQ, client=self.parent) - self.xmlstream.addObserver(MSG_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestMsg, client=self.parent) + self.xmlstream.addObserver( + IQ_HTTP_AUTH_REQUEST, + self.plugin_parent.onHttpAuthRequestIQ, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_HTTP_AUTH_REQUEST, + self.plugin_parent.onHttpAuthRequestMsg, + client=self.parent, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HTTP_AUTH)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0071.py b/sat/plugins/plugin_xep_0071.py index fa9b5ee3..5773066e 100644 --- a/sat/plugins/plugin_xep_0071.py +++ b/sat/plugins/plugin_xep_0071.py @@ -21,24 +21,28 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools.common import data_format from twisted.internet import defer from wokkel import disco, iwokkel from zope.interface import implements + # from lxml import etree try: from lxml import html except ImportError: - raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/") + raise exceptions.MissingModule( + u"Missing module lxml, please download/install it from http://lxml.de/" + ) try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' -NS_XHTML = 'http://www.w3.org/1999/xhtml' +NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im" +NS_XHTML = "http://www.w3.org/1999/xhtml" PLUGIN_INFO = { C.PI_NAME: "XHTML-IM Plugin", @@ -48,28 +52,39 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["TEXT-SYNTAXES"], C.PI_MAIN: "XEP_0071", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of XHTML-IM""") + C.PI_DESCRIPTION: _("""Implementation of XHTML-IM"""), } allowed = { - "a": set(["href", "style", "type"]), - "blockquote": set(["style"]), - "body": set(["style"]), - "br": set([]), - "cite": set(["style"]), - "em": set([]), - "img": set(["alt", "height", "src", "style", "width"]), - "li": set(["style"]), - "ol": set(["style"]), - "p": set(["style"]), - "span": set(["style"]), - "strong": set([]), - "ul": set(["style"]), - } + "a": set(["href", "style", "type"]), + "blockquote": set(["style"]), + "body": set(["style"]), + "br": set([]), + "cite": set(["style"]), + "em": set([]), + "img": set(["alt", "height", "src", "style", "width"]), + "li": set(["style"]), + "ol": set(["style"]), + "p": set(["style"]), + "span": set(["style"]), + "strong": set([]), + "ul": set(["style"]), +} -styles_allowed = ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"] +styles_allowed = [ + "background-color", + "color", + "font-family", + "font-size", + "font-style", + "font-weight", + "margin-left", + "margin-right", + "text-align", + "text-decoration", +] -blacklist = ['script'] # tag that we have to kill (we don't keep content) +blacklist = ["script"] # tag that we have to kill (we don't keep content) class XEP_0071(object): @@ -79,7 +94,12 @@ class XEP_0071(object): log.info(_("XHTML-IM plugin initialization")) self.host = host self._s = self.host.plugins["TEXT-SYNTAXES"] - self._s.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self._s.OPT_HIDDEN]) + self._s.addSyntax( + self.SYNTAX_XHTML_IM, + lambda xhtml: xhtml, + self.XHTML2XHTML_IM, + [self._s.OPT_HIDDEN], + ) host.trigger.add("MessageReceived", self.messageReceivedTrigger) host.trigger.add("sendMessage", self.sendMessageTrigger) @@ -97,17 +117,23 @@ class XEP_0071(object): # TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message def converted(xhtml, lang): if lang: - data['extra']['xhtml_{}'.format(lang)] = xhtml + data["extra"]["xhtml_{}".format(lang)] = xhtml else: - data['extra']['xhtml'] = xhtml + data["extra"]["xhtml"] = xhtml defers = [] for body_elt in body_elts: - lang = body_elt.getAttribute((C.NS_XML, 'lang'), '') - treat_d = defer.succeed(None) # deferred used for treatments - if self.host.trigger.point("xhtml_post_treat", client, message_elt, body_elt, lang, treat_d): + lang = body_elt.getAttribute((C.NS_XML, "lang"), "") + treat_d = defer.succeed(None) # deferred used for treatments + if self.host.trigger.point( + "xhtml_post_treat", client, message_elt, body_elt, lang, treat_d + ): continue - treat_d.addCallback(lambda dummy: self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True)) + treat_d.addCallback( + lambda dummy: self._s.convert( + body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True + ) + ) treat_d.addCallback(converted, lang) defers.append(treat_d) @@ -116,15 +142,15 @@ class XEP_0071(object): return d_list def _fill_body_text(self, text, data, lang): - data['message'][lang or ''] = text - message_elt = data['xml'] + data["message"][lang or ""] = text + message_elt = data["xml"] body_elt = message_elt.addElement("body", content=text) if lang: - body_elt[(C.NS_XML, 'lang')] = lang + body_elt[(C.NS_XML, "lang")] = lang def _check_body_text(self, data, lang, markup, syntax, defers): """check if simple text message exists, and fill if needed""" - if not (lang or '') in data['message']: + if not (lang or "") in data["message"]: d = self._s.convert(markup, syntax, self._s.SYNTAX_TEXT) d.addCallback(self._fill_body_text, data, lang) defers.append(d) @@ -136,30 +162,30 @@ class XEP_0071(object): """ # at this point, either ['extra']['rich'] or ['extra']['xhtml'] exists # but both can't exist at the same time - message_elt = data['xml'] - html_elt = message_elt.addElement((NS_XHTML_IM, 'html')) + message_elt = data["xml"] + html_elt = message_elt.addElement((NS_XHTML_IM, "html")) def syntax_converted(xhtml_im, lang): - body_elt = html_elt.addElement((NS_XHTML, 'body')) + body_elt = html_elt.addElement((NS_XHTML, "body")) if lang: - body_elt[(C.NS_XML, 'lang')] = lang - data['extra']['xhtml_{}'.format(lang)] = xhtml_im + body_elt[(C.NS_XML, "lang")] = lang + data["extra"]["xhtml_{}".format(lang)] = xhtml_im else: - data['extra']['xhtml'] = xhtml_im + data["extra"]["xhtml"] = xhtml_im body_elt.addRawXml(xhtml_im) syntax = self._s.getCurrentSyntax(client.profile) defers = [] - if u'xhtml' in data['extra']: + if u"xhtml" in data["extra"]: # we have directly XHTML - for lang, xhtml in data_format.getSubDict('xhtml', data['extra']): + for lang, xhtml in data_format.getSubDict("xhtml", data["extra"]): self._check_body_text(data, lang, xhtml, self._s.SYNTAX_XHTML, defers) d = self._s.convert(xhtml, self._s.SYNTAX_XHTML, self.SYNTAX_XHTML_IM) d.addCallback(syntax_converted, lang) defers.append(d) - elif u'rich' in data['extra']: + elif u"rich" in data["extra"]: # we have rich syntax to convert - for lang, rich_data in data_format.getSubDict('rich', data['extra']): + for lang, rich_data in data_format.getSubDict("rich", data["extra"]): self._check_body_text(data, lang, rich_data, syntax, defers) d = self._s.convert(rich_data, syntax, self.SYNTAX_XHTML_IM) d.addCallback(syntax_converted, lang) @@ -174,12 +200,12 @@ class XEP_0071(object): """ Check presence of XHTML-IM in message """ try: - html_elt = message.elements(NS_XHTML_IM, 'html').next() + html_elt = message.elements(NS_XHTML_IM, "html").next() except StopIteration: # No XHTML-IM pass else: - body_elts = html_elt.elements(NS_XHTML, 'body') + body_elts = html_elt.elements(NS_XHTML, "body") post_treat.addCallback(self._messagePostTreat, message, body_elts, client) return True @@ -187,18 +213,20 @@ class XEP_0071(object): """ Check presence of rich text in extra """ rich = {} xhtml = {} - for key, value in data['extra'].iteritems(): - if key.startswith('rich'): + for key, value in data["extra"].iteritems(): + if key.startswith("rich"): rich[key[5:]] = value - elif key.startswith('xhtml'): + elif key.startswith("xhtml"): xhtml[key[6:]] = value if rich and xhtml: - raise exceptions.DataError(_(u"Can't have XHTML and rich content at the same time")) + raise exceptions.DataError( + _(u"Can't have XHTML and rich content at the same time") + ) if rich or xhtml: if rich: - data['rich'] = rich + data["rich"] = rich else: - data['xhtml'] = xhtml + data["xhtml"] = xhtml post_xml_treatments.addCallback(self._sendMessageAddRich, client) return True @@ -208,7 +236,7 @@ class XEP_0071(object): """ purged = [] - styles = [style.strip().split(':') for style in styles_raw.split(';')] + styles = [style.strip().split(":") for style in styles_raw.split(";")] for style_tuple in styles: if len(style_tuple) != 2: @@ -219,7 +247,7 @@ class XEP_0071(object): continue purged.append((name, value.strip())) - return u'; '.join([u"%s: %s" % data for data in purged]) + return u"; ".join([u"%s: %s" % data for data in purged]) def XHTML2XHTML_IM(self, xhtml): """ Convert XHTML document to XHTML_IM subset @@ -227,12 +255,12 @@ class XEP_0071(object): """ # TODO: more clever tag replacement (replace forbidden tags with equivalents when possible) - parser = html.HTMLParser(remove_comments=True, encoding='utf-8') + parser = html.HTMLParser(remove_comments=True, encoding="utf-8") root = html.fromstring(xhtml, parser=parser) - body_elt = root.find('body') + body_elt = root.find("body") if body_elt is None: # we use the whole XML as body if no body element is found - body_elt = html.Element('body') + body_elt = html.Element("body") body_elt.append(root) else: body_elt.attrib.clear() @@ -247,24 +275,25 @@ class XEP_0071(object): attrib = elem.attrib att_to_remove = set(attrib).difference(allowed[elem.tag]) for att in att_to_remove: - del(attrib[att]) + del (attrib[att]) if "style" in attrib: attrib["style"] = self._purgeStyle(attrib["style"]) for elem in to_strip: if elem.tag in blacklist: - #we need to remove the element and all descendants + # we need to remove the element and all descendants log.debug(u"removing black listed tag: %s" % (elem.tag)) elem.drop_tree() else: elem.drop_tag() - if len(body_elt) !=1: + if len(body_elt) != 1: root_elt = body_elt body_elt.tag = "p" else: root_elt = body_elt[0] - return html.tostring(root_elt, encoding='unicode', method='xml') + return html.tostring(root_elt, encoding="unicode", method="xml") + class XEP_0071_handler(XMPPHandler): implements(iwokkel.IDisco) @@ -273,8 +302,8 @@ class XEP_0071_handler(XMPPHandler): self.plugin_parent = plugin_parent self.host = plugin_parent.host - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_XHTML_IM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0077.py b/sat/plugins/plugin_xep_0077.py index 98eea989..56927abb 100755 --- a/sat/plugins/plugin_xep_0077.py +++ b/sat/plugins/plugin_xep_0077.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber import xmlstream @@ -29,7 +30,7 @@ from sat.tools import xml_tools from wokkel import data_form -NS_REG = 'jabber:iq:register' +NS_REG = "jabber:iq:register" PLUGIN_INFO = { C.PI_NAME: "XEP 0077 Plugin", @@ -38,11 +39,12 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0077"], C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0077", - C.PI_DESCRIPTION: _("""Implementation of in-band registration""") + C.PI_DESCRIPTION: _("""Implementation of in-band registration"""), } # FIXME: this implementation is incomplete + class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): # FIXME: request IQ is not send to check available fields, while XEP recommand to use it # FIXME: doesn't handle data form or oob @@ -53,8 +55,7 @@ class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): self.password = password self.email = email self.registered = defer.Deferred() - log.debug(_(u"Registration asked for {jid}").format( - jid = jid_)) + log.debug(_(u"Registration asked for {jid}").format(jid=jid_)) def connectionMade(self): log.debug(_(u"Connection made with {server}".format(server=self.jid.host))) @@ -77,57 +78,78 @@ class RegisteringAuthenticator(xmlstream.ConnectAuthenticator): class XEP_0077(object): - def __init__(self, host): log.info(_("Plugin XEP_0077 initialization")) self.host = host - host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='', - method=self._inBandRegister, - async=True) - host.bridge.addMethod("inBandAccountNew", ".plugin", in_sign='ssssi', out_sign='', - method=self._registerNewAccount, - async=True) - host.bridge.addMethod("inBandUnregister", ".plugin", in_sign='ss', out_sign='', - method=self._unregister, - async=True) - host.bridge.addMethod("inBandPasswordChange", ".plugin", in_sign='ss', out_sign='', - method=self._changePassword, - async=True) + host.bridge.addMethod( + "inBandRegister", + ".plugin", + in_sign="ss", + out_sign="", + method=self._inBandRegister, + async=True, + ) + host.bridge.addMethod( + "inBandAccountNew", + ".plugin", + in_sign="ssssi", + out_sign="", + method=self._registerNewAccount, + async=True, + ) + host.bridge.addMethod( + "inBandUnregister", + ".plugin", + in_sign="ss", + out_sign="", + method=self._unregister, + async=True, + ) + host.bridge.addMethod( + "inBandPasswordChange", + ".plugin", + in_sign="ss", + out_sign="", + method=self._changePassword, + async=True, + ) @staticmethod def buildRegisterIQ(xmlstream_, jid_, password, email=None): - iq_elt = xmlstream.IQ(xmlstream_, 'set') + iq_elt = xmlstream.IQ(xmlstream_, "set") iq_elt["to"] = jid_.host - query_elt = iq_elt.addElement(('jabber:iq:register', 'query')) - username_elt = query_elt.addElement('username') + query_elt = iq_elt.addElement(("jabber:iq:register", "query")) + username_elt = query_elt.addElement("username") username_elt.addContent(jid_.user) - password_elt = query_elt.addElement('password') + password_elt = query_elt.addElement("password") password_elt.addContent(password) if email is not None: - email_elt = query_elt.addElement('email') + email_elt = query_elt.addElement("email") email_elt.addContent(email) return iq_elt def _regCb(self, answer, client, post_treat_cb): """Called after the first get IQ""" try: - query_elt = answer.elements(NS_REG, 'query').next() + query_elt = answer.elements(NS_REG, "query").next() except StopIteration: raise exceptions.DataError("Can't find expected query element") try: - x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next() + x_elem = query_elt.elements(data_form.NS_X_DATA, "x").next() except StopIteration: # XXX: it seems we have an old service which doesn't manage data forms log.warning(_("Can't find data form")) - raise exceptions.DataError(_("This gateway can't be managed by SàT, sorry :(")) + raise exceptions.DataError( + _("This gateway can't be managed by SàT, sorry :(") + ) def submitForm(data, profile): form_elt = xml_tools.XMLUIResultToElt(data) iq_elt = client.IQ() - iq_elt['id'] = answer['id'] - iq_elt['to'] = answer['from'] + iq_elt["id"] = answer["id"] + iq_elt["to"] = answer["from"] query_elt = iq_elt.addElement("query", NS_REG) query_elt.addChild(form_elt) d = iq_elt.send() @@ -136,7 +158,9 @@ class XEP_0077(object): return d form = data_form.Form.fromElement(x_elem) - submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True) + submit_reg_id = self.host.registerCallback( + submitForm, with_data=True, one_shot=True + ) return xml_tools.dataForm2XMLUI(form, submit_reg_id) def _regEb(self, failure, client): @@ -147,13 +171,15 @@ class XEP_0077(object): def _regSuccess(self, answer, client, post_treat_cb): log.debug(_(u"registration answer: %s") % answer.toXml()) if post_treat_cb is not None: - post_treat_cb(jid.JID(answer['from']), client.profile) + post_treat_cb(jid.JID(answer["from"]), client.profile) return {} def _regFailure(self, failure, client): log.info(_(u"Registration failure: %s") % unicode(failure.value)) - if failure.value.condition == 'conflict': - raise exceptions.ConflictError( _("Username already exists, please choose an other one")) + if failure.value.condition == "conflict": + raise exceptions.ConflictError( + _("Username already exists, please choose an other one") + ) raise failure def _inBandRegister(self, to_jid_s, profile_key=C.PROF_KEY_NONE): @@ -167,24 +193,31 @@ class XEP_0077(object): # FIXME: this post_treat_cb arguments seems wrong, check it client = self.host.getClient(profile_key) log.debug(_(u"Asking registration for {}").format(to_jid.full())) - reg_request = client.IQ(u'get') + reg_request = client.IQ(u"get") reg_request["from"] = client.jid.full() reg_request["to"] = to_jid.full() - reg_request.addElement('query', NS_REG) - d = reg_request.send(to_jid.full()).addCallbacks(self._regCb, self._regEb, callbackArgs=[client, post_treat_cb], errbackArgs=[client]) + reg_request.addElement("query", NS_REG) + d = reg_request.send(to_jid.full()).addCallbacks( + self._regCb, + self._regEb, + callbackArgs=[client, post_treat_cb], + errbackArgs=[client], + ) return d def _registerNewAccount(self, jid_, password, email, host, port): kwargs = {} if email: - kwargs['email'] = email + kwargs["email"] = email if host: - kwargs['host'] = host + kwargs["host"] = host if port: - kwargs['port'] = port + kwargs["port"] = port return self.registerNewAccount(jid.JID(jid_), password, **kwargs) - def registerNewAccount(self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT): + def registerNewAccount( + self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT + ): """register a new account on a XMPP server @param jid_(jid.JID): request jid to register @@ -207,7 +240,11 @@ class XEP_0077(object): def changePassword(self, client, new_password): iq_elt = self.buildRegisterIQ(client.xmlstream, client.jid, new_password) d = iq_elt.send(client.jid.host) - d.addCallback(lambda dummy: self.host.memory.setParam("Password", new_password, "Connection", profile_key=client.profile)) + d.addCallback( + lambda dummy: self.host.memory.setParam( + "Password", new_password, "Connection", profile_key=client.profile + ) + ) return d def _unregister(self, to_jid_s, profile_key): @@ -222,7 +259,7 @@ class XEP_0077(object): @param to_jid(jid.JID): jid of the service or server """ iq_elt = client.IQ() - iq_elt['to'] = to_jid.full() - query_elt = iq_elt.addElement((NS_REG, u'query')) - query_elt.addElement(u'remove') + iq_elt["to"] = to_jid.full() + query_elt = iq_elt.addElement((NS_REG, u"query")) + query_elt.addElement(u"remove") return iq_elt.send() diff --git a/sat/plugins/plugin_xep_0085.py b/sat/plugins/plugin_xep_0085.py index aa806eb9..2f0678e6 100755 --- a/sat/plugins/plugin_xep_0085.py +++ b/sat/plugins/plugin_xep_0085.py @@ -21,10 +21,12 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements from twisted.words.protocols.jabber.jid import JID + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -50,7 +52,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0085", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol""") + C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol"""), } @@ -63,7 +65,7 @@ TRANSITIONS = { "inactive": {"next_state": "gone", "delay": 480}, "gone": {"next_state": "", "delay": 0}, "composing": {"next_state": "paused", "delay": 30}, - "paused": {"next_state": "inactive", "delay": 450} + "paused": {"next_state": "inactive", "delay": 450}, } @@ -71,6 +73,7 @@ class UnknownChatStateException(Exception): """ This error is raised when an unknown chat state is used. """ + pass @@ -78,6 +81,7 @@ class XEP_0085(object): """ Implementation for XEP 0085 """ + params = """ """ % { - 'category_name': PARAM_KEY, - 'category_label': _(PARAM_KEY), - 'param_name': PARAM_NAME, - 'param_label': _('Enable chat state notifications') + "category_name": PARAM_KEY, + "category_label": _(PARAM_KEY), + "param_name": PARAM_NAME, + "param_label": _("Enable chat state notifications"), } def __init__(self, host): @@ -107,11 +111,16 @@ class XEP_0085(object): host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger) # args: to_s (jid as string), profile - host.bridge.addMethod("chatStateComposing", ".plugin", in_sign='ss', - out_sign='', method=self.chatStateComposing) + host.bridge.addMethod( + "chatStateComposing", + ".plugin", + in_sign="ss", + out_sign="", + method=self.chatStateComposing, + ) # args: from (jid as string), state in CHAT_STATES, profile - host.bridge.addSignal("chatStateReceived", ".plugin", signature='sss') + host.bridge.addSignal("chatStateReceived", ".plugin", signature="sss") def getHandler(self, client): return XEP_0085_handler(self, client.profile) @@ -125,7 +134,7 @@ class XEP_0085(object): # FIXME: the "unavailable" presence stanza is received by to_jid # before the chat state, so it will be ignored... find a way to # actually defer the disconnection - self.map[profile][to_jid]._onEvent('gone') + self.map[profile][to_jid]._onEvent("gone") del self.map[profile] def updateCache(self, entity_jid, value, profile): @@ -139,7 +148,9 @@ class XEP_0085(object): if value == DELETE_VALUE: self.host.memory.delEntityDatum(entity_jid, ENTITY_KEY, profile) else: - self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile_key=profile) + self.host.memory.updateEntityData( + entity_jid, ENTITY_KEY, value, profile_key=profile + ) if not value or value == DELETE_VALUE: # reinit chat state UI for this or these contact(s) self.host.bridge.chatStateReceived(entity_jid.full(), "", profile) @@ -153,7 +164,9 @@ class XEP_0085(object): @param type_: parameter type """ if (category, name) == (PARAM_KEY, PARAM_NAME): - self.updateCache(C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile) + self.updateCache( + C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile + ) return False return True @@ -178,7 +191,7 @@ class XEP_0085(object): # contact enabled Chat State Notifications self.updateCache(from_jid, True, profile=profile) except StopIteration: - if message.getAttribute('type') == 'chat': + if message.getAttribute("type") == "chat": # contact didn't enable Chat State Notifications self.updateCache(from_jid, False, profile=profile) return True @@ -188,32 +201,40 @@ class XEP_0085(object): # send our next "composing" states to any MUC and to the contacts who enabled the feature self._chatStateInit(from_jid, message.getAttribute("type"), profile) - state_list = [child.name for child in message.elements() if - message.getAttribute("type") in MESSAGE_TYPES - and child.name in CHAT_STATES - and child.defaultUri == NS_CHAT_STATES] + state_list = [ + child.name + for child in message.elements() + if message.getAttribute("type") in MESSAGE_TYPES + and child.name in CHAT_STATES + and child.defaultUri == NS_CHAT_STATES + ] for state in state_list: # there must be only one state according to the XEP - if state != 'gone' or message.getAttribute('type') != 'groupchat': - self.host.bridge.chatStateReceived(message.getAttribute("from"), state, profile) + if state != "gone" or message.getAttribute("type") != "groupchat": + self.host.bridge.chatStateReceived( + message.getAttribute("from"), state, profile + ) break return True - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """ Eventually add the chat state to the message and initiate the state machine when sending an "active" state. """ profile = client.profile + def treatment(mess_data): - message = mess_data['xml'] + message = mess_data["xml"] to_jid = JID(message.getAttribute("to")) if not self._checkActivation(to_jid, forceEntityData=True, profile=profile): return mess_data try: # message with a body always mean active state domish.generateElementsNamed(message.elements(), name="body").next() - message.addElement('active', NS_CHAT_STATES) + message.addElement("active", NS_CHAT_STATES) # launch the chat state machine (init the timer) if self._isMUC(to_jid, profile): to_jid = to_jid.userhostJID() @@ -236,8 +257,8 @@ class XEP_0085(object): @return: bool """ try: - type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile) - if type_ == 'chatroom': # FIXME: should not use disco instead ? + type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), "type", profile) + if type_ == "chatroom": # FIXME: should not use disco instead ? return True except (exceptions.UnknownEntityError, KeyError): pass @@ -281,8 +302,7 @@ class XEP_0085(object): return profile_map = self.map.setdefault(profile, {}) if to_jid not in profile_map: - machine = ChatStateMachine(self.host, to_jid, - mess_type, profile) + machine = ChatStateMachine(self.host, to_jid, mess_type, profile) self.map[profile][to_jid] = machine def _chatStateActive(self, to_jid, mess_type, profile_key): @@ -316,7 +336,9 @@ class XEP_0085(object): to_jid = to_jid.userhostJID() elif not to_jid.resource: to_jid.resource = self.host.memory.getMainResource(client, to_jid) - if not self._checkActivation(to_jid, forceEntityData=False, profile=client.profile): + if not self._checkActivation( + to_jid, forceEntityData=False, profile=client.profile + ): return try: self.map[client.profile][to_jid]._onEvent("composing") @@ -359,22 +381,26 @@ class ChatStateMachine(object): assert "next_state" in transition and "delay" in transition if state != self.state and state != "active": - if state != 'gone' or self.mess_type != 'groupchat': + if state != "gone" or self.mess_type != "groupchat": # send a new message without body - log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full())) + log.debug( + u"sending state '{state}' to {jid}".format( + state=state, jid=self.to_jid.full() + ) + ) client = self.host.getClient(self.profile) mess_data = { - 'from': client.jid, - 'to': self.to_jid, - 'uid': '', - 'message': {}, - 'type': self.mess_type, - 'subject': {}, - 'extra': {}, - } + "from": client.jid, + "to": self.to_jid, + "uid": "", + "message": {}, + "type": self.mess_type, + "subject": {}, + "extra": {}, + } client.generateMessageXML(mess_data) - mess_data['xml'].addElement(state, NS_CHAT_STATES) - client.send(mess_data['xml']) + mess_data["xml"].addElement(state, NS_CHAT_STATES) + client.send(mess_data["xml"]) self.state = state try: @@ -383,7 +409,9 @@ class ChatStateMachine(object): pass if transition["next_state"] and transition["delay"] > 0: - self.timer = reactor.callLater(transition["delay"], self._onEvent, transition["next_state"]) + self.timer = reactor.callLater( + transition["delay"], self._onEvent, transition["next_state"] + ) class XEP_0085_handler(XMPPHandler): @@ -394,8 +422,8 @@ class XEP_0085_handler(XMPPHandler): self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_CHAT_STATES)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0092.py b/sat/plugins/plugin_xep_0092.py index 68779216..e36692b3 100644 --- a/sat/plugins/plugin_xep_0092.py +++ b/sat/plugins/plugin_xep_0092.py @@ -24,6 +24,7 @@ from twisted.words.protocols.jabber import jid from wokkel import compat from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) NS_VERSION = "jabber:iq:version" @@ -37,17 +38,23 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], C.PI_MAIN: "XEP_0092", - C.PI_HANDLER: "no", # version is already handler in core.xmpp module - C.PI_DESCRIPTION: _("""Implementation of Software Version""") + C.PI_HANDLER: "no", # version is already handler in core.xmpp module + C.PI_DESCRIPTION: _("""Implementation of Software Version"""), } class XEP_0092(object): - def __init__(self, host): log.info(_("Plugin XEP_0092 initialization")) self.host = host - host.bridge.addMethod("getSoftwareVersion", ".plugin", in_sign='ss', out_sign='(sss)', method=self._getVersion, async=True) + host.bridge.addMethod( + "getSoftwareVersion", + ".plugin", + in_sign="ss", + out_sign="(sss)", + method=self._getVersion, + async=True, + ) try: self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 50) except KeyError: @@ -56,7 +63,8 @@ class XEP_0092(object): def _getVersion(self, entity_jid_s, profile_key): def prepareForBridge(data): name, version, os = data - return (name or '', version or '', os or '') + return (name or "", version or "", os or "") + d = self.getVersion(jid.JID(entity_jid_s), profile_key) d.addCallback(prepareForBridge) return d @@ -71,25 +79,29 @@ class XEP_0092(object): - os: operating system of the queried entity """ client = self.host.getClient(profile_key) + def getVersion(dummy): - iq_elt = compat.IQ(client.xmlstream, 'get') - iq_elt['to'] = jid_.full() + iq_elt = compat.IQ(client.xmlstream, "get") + iq_elt["to"] = jid_.full() iq_elt.addElement("query", NS_VERSION) d = iq_elt.send() d.addCallback(self._gotVersion) return d + d = self.host.checkFeature(client, NS_VERSION, jid_) d.addCallback(getVersion) - reactor.callLater(TIMEOUT, d.cancel) # XXX: timeout needed because some clients don't answer the IQ + reactor.callLater( + TIMEOUT, d.cancel + ) # XXX: timeout needed because some clients don't answer the IQ return d def _gotVersion(self, iq_elt): try: - query_elt = iq_elt.elements(NS_VERSION, 'query').next() + query_elt = iq_elt.elements(NS_VERSION, "query").next() except StopIteration: raise exceptions.DataError ret = [] - for name in ('name', 'version', 'os'): + for name in ("name", "version", "os"): try: data_elt = query_elt.elements(NS_VERSION, name).next() ret.append(unicode(data_elt)) @@ -98,9 +110,9 @@ class XEP_0092(object): return tuple(ret) - def _whois(self, client, whois_msg, mess_data, target_jid): """ Add software/OS information to whois """ + def versionCb(version_data): name, version, os = version_data if name: @@ -109,9 +121,10 @@ class XEP_0092(object): whois_msg.append(_("Client version: %s") % version) if os: whois_msg.append(_("Operating system: %s") % os) + def versionEb(failure): failure.trap(exceptions.FeatureNotFound, defer.CancelledError) - if failure.check(failure,exceptions.FeatureNotFound): + if failure.check(failure, exceptions.FeatureNotFound): whois_msg.append(_("Software version not available")) else: whois_msg.append(_("Client software version request timeout")) @@ -119,4 +132,3 @@ class XEP_0092(object): d = self.getVersion(target_jid, client.profile) d.addCallbacks(versionCb, versionEb) return d - diff --git a/sat/plugins/plugin_xep_0095.py b/sat/plugins/plugin_xep_0095.py index b343ac26..9ccd73d7 100644 --- a/sat/plugins/plugin_xep_0095.py +++ b/sat/plugins/plugin_xep_0095.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.protocols.jabber import xmlstream @@ -37,19 +38,18 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0095"], C.PI_MAIN: "XEP_0095", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Stream Initiation""") + C.PI_DESCRIPTION: _("""Implementation of Stream Initiation"""), } IQ_SET = '/iq[@type="set"]' -NS_SI = 'http://jabber.org/protocol/si' +NS_SI = "http://jabber.org/protocol/si" SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]' SI_PROFILE_HEADER = "http://jabber.org/protocol/si/profile/" -SI_ERROR_CONDITIONS = ('bad-profile', 'no-valid-streams') +SI_ERROR_CONDITIONS = ("bad-profile", "no-valid-streams") class XEP_0095(object): - def __init__(self, host): log.info(_("Plugin XEP_0095 initialization")) self.host = host @@ -70,7 +70,11 @@ class XEP_0095(object): try: del self.si_profiles[si_profile] except KeyError: - log.error(u"Trying to unregister SI profile [{}] which was not registered".format(si_profile)) + log.error( + u"Trying to unregister SI profile [{}] which was not registered".format( + si_profile + ) + ) def streamInit(self, iq_elt, client): """This method is called on stream initiation (XEP-0095 #3.2) @@ -79,17 +83,21 @@ class XEP_0095(object): """ log.info(_("XEP-0095 Stream initiation")) iq_elt.handled = True - si_elt = iq_elt.elements(NS_SI, 'si').next() - si_id = si_elt['id'] - si_mime_type = iq_elt.getAttribute('mime-type', 'application/octet-stream') - si_profile = si_elt['profile'] - si_profile_key = si_profile[len(SI_PROFILE_HEADER):] if si_profile.startswith(SI_PROFILE_HEADER) else si_profile + si_elt = iq_elt.elements(NS_SI, "si").next() + si_id = si_elt["id"] + si_mime_type = iq_elt.getAttribute("mime-type", "application/octet-stream") + si_profile = si_elt["profile"] + si_profile_key = ( + si_profile[len(SI_PROFILE_HEADER) :] + if si_profile.startswith(SI_PROFILE_HEADER) + else si_profile + ) if si_profile_key in self.si_profiles: - #We know this SI profile, we call the callback + # We know this SI profile, we call the callback self.si_profiles[si_profile_key](client, iq_elt, si_id, si_mime_type, si_elt) else: - #We don't know this profile, we send an error - self.sendError(client, iq_elt, 'bad-profile') + # We don't know this profile, we send an error + self.sendError(client, iq_elt, "bad-profile") def sendError(self, client, request, condition): """Send IQ error as a result @@ -99,7 +107,7 @@ class XEP_0095(object): """ if condition in SI_ERROR_CONDITIONS: si_condition = condition - condition = 'bad-request' + condition = "bad-request" else: si_condition = None @@ -119,8 +127,8 @@ class XEP_0095(object): log.info(_("sending stream initiation accept answer")) if misc_elts is None: misc_elts = [] - result_elt = xmlstream.toResponse(iq_elt, 'result') - si_elt = result_elt.addElement((NS_SI, 'si')) + result_elt = xmlstream.toResponse(iq_elt, "result") + si_elt = result_elt.addElement((NS_SI, "si")) si_elt.addChild(feature_elt) for elt in misc_elts: si_elt.addChild(elt) @@ -134,8 +142,15 @@ class XEP_0095(object): raise exceptions.DataError return (iq_elt, si_elt) - - def proposeStream(self, client, to_jid, si_profile, feature_elt, misc_elts, mime_type='application/octet-stream'): + def proposeStream( + self, + client, + to_jid, + si_profile, + feature_elt, + misc_elts, + mime_type="application/octet-stream", + ): """Propose a stream initiation @param to_jid(jid.JID): recipient @@ -154,8 +169,8 @@ class XEP_0095(object): offer["from"] = client.jid.full() offer["to"] = to_jid.full() - si = offer.addElement('si', NS_SI) - si['id'] = sid + si = offer.addElement("si", NS_SI) + si["id"] = sid si["mime-type"] = mime_type si["profile"] = si_profile for elt in misc_elts: @@ -175,10 +190,17 @@ class XEP_0095_handler(xmlstream.XMPPHandler): self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.streamInit, client=self.parent) + self.xmlstream.addObserver( + SI_REQUEST, self.plugin_parent.streamInit, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - return [disco.DiscoFeature(NS_SI)] + [disco.DiscoFeature(u"http://jabber.org/protocol/si/profile/{}".format(profile_name)) for profile_name in self.plugin_parent.si_profiles] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + return [disco.DiscoFeature(NS_SI)] + [ + disco.DiscoFeature( + u"http://jabber.org/protocol/si/profile/{}".format(profile_name) + ) + for profile_name in self.plugin_parent.si_profiles + ] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0096.py b/sat/plugins/plugin_xep_0096.py index 1cbf7b92..4c36fffa 100755 --- a/sat/plugins/plugin_xep_0096.py +++ b/sat/plugins/plugin_xep_0096.py @@ -20,6 +20,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -43,7 +44,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0020", "XEP-0095", "XEP-0065", "XEP-0047", "FILE"], C.PI_MAIN: "XEP_0096", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of SI File Transfer""") + C.PI_DESCRIPTION: _("""Implementation of SI File Transfer"""), } @@ -53,13 +54,19 @@ class XEP_0096(object): def __init__(self, host): log.info(_("Plugin XEP_0096 initialization")) self.host = host - self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE, - self.host.plugins["XEP-0047"].NAMESPACE] # Stream methods managed + self.managed_stream_m = [ + self.host.plugins["XEP-0065"].NAMESPACE, + self.host.plugins["XEP-0047"].NAMESPACE, + ] # Stream methods managed self._f = self.host.plugins["FILE"] - self._f.register(NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation") + self._f.register( + NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation" + ) self._si = self.host.plugins["XEP-0095"] self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest) - host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile) + host.bridge.addMethod( + "siSendFile", ".plugin", in_sign="sssss", out_sign="s", method=self._sendFile + ) def unload(self): self._si.unregisterSIProfile(SI_PROFILE_NAME) @@ -72,7 +79,7 @@ class XEP_0096(object): """ if message is not None: log.warning(message) - self._si.sendError(client, iq_elt, 'bad-request') + self._si.sendError(client, iq_elt, "bad-request") def _parseRange(self, parent_elt, file_size): """find and parse @@ -87,10 +91,10 @@ class XEP_0085(object): element @@ -84,7 +91,7 @@ class XEP_0096(object): - range_length """ try: - range_elt = parent_elt.elements(NS_SI_FT, 'range').next() + range_elt = parent_elt.elements(NS_SI_FT, "range").next() except StopIteration: range_ = False range_offset = None @@ -93,17 +100,17 @@ class XEP_0096(object): range_ = True try: - range_offset = int(range_elt['offset']) + range_offset = int(range_elt["offset"]) except KeyError: range_offset = 0 try: - range_length = int(range_elt['length']) + range_length = int(range_elt["length"]) except KeyError: range_length = file_size if range_offset != 0 or range_length != file_size: - raise NotImplementedError # FIXME + raise NotImplementedError # FIXME return range_, range_offset, range_length @@ -116,17 +123,21 @@ class XEP_0096(object): @param si_elt(domish.Element): request """ log.info(_("XEP-0096 file transfer requested")) - peer_jid = jid.JID(iq_elt['from']) + peer_jid = jid.JID(iq_elt["from"]) try: file_elt = si_elt.elements(NS_SI_FT, "file").next() except StopIteration: - return self._badRequest(client, iq_elt, "No element found in SI File Transfer request") + return self._badRequest( + client, iq_elt, "No element found in SI File Transfer request" + ) try: feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt) except exceptions.NotFound: - return self._badRequest(client, iq_elt, "No element found in SI File Transfer request") + return self._badRequest( + client, iq_elt, "No element found in SI File Transfer request" + ) try: filename = file_elt["name"] @@ -137,12 +148,16 @@ class XEP_0096(object): file_date = file_elt.getAttribute("date") file_hash = file_elt.getAttribute("hash") - log.info(u"File proposed: name=[{name}] size={size}".format(name=filename, size=file_size)) + log.info( + u"File proposed: name=[{name}] size={size}".format( + name=filename, size=file_size + ) + ) try: - file_desc = unicode(file_elt.elements(NS_SI_FT, 'desc').next()) + file_desc = unicode(file_elt.elements(NS_SI_FT, "desc").next()) except StopIteration: - file_desc = '' + file_desc = "" try: range_, range_offset, range_length = self._parseRange(file_elt, file_size) @@ -150,7 +165,9 @@ class XEP_0096(object): return self._badRequest(client, iq_elt, "Malformed SI File Transfer request") try: - stream_method = self.host.plugins["XEP-0020"].negotiate(feature_elt, 'stream-method', self.managed_stream_m, namespace=None) + stream_method = self.host.plugins["XEP-0020"].negotiate( + feature_elt, "stream-method", self.managed_stream_m, namespace=None + ) except KeyError: return self._badRequest(client, iq_elt, "No stream method found") @@ -160,16 +177,30 @@ class XEP_0096(object): elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE: plugin = self.host.plugins["XEP-0047"] else: - log.error(u"Unknown stream method, this should not happen at this stage, cancelling transfer") + log.error( + u"Unknown stream method, this should not happen at this stage, cancelling transfer" + ) else: log.warning(u"Can't find a valid stream method") - self._si.sendError(client, iq_elt, 'not-acceptable') + self._si.sendError(client, iq_elt, "not-acceptable") return - #if we are here, the transfer can start, we just need user's agreement - data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc, - "range": range_, "range_offset": range_offset, "range_length": range_length, - "si_id": si_id, "progress_id": si_id, "stream_method": stream_method, "stream_plugin": plugin} + # if we are here, the transfer can start, we just need user's agreement + data = { + "name": filename, + "peer_jid": peer_jid, + "size": file_size, + "date": file_date, + "hash": file_hash, + "desc": file_desc, + "range": range_, + "range_offset": range_offset, + "range_length": range_length, + "si_id": si_id, + "progress_id": si_id, + "stream_method": stream_method, + "stream_plugin": plugin, + } d = self._f.getDestDir(client, peer_jid, data, data, stream_object=True) d.addCallback(self.confirmationCb, client, iq_elt, data) @@ -183,7 +214,7 @@ class XEP_0096(object): """ if not accepted: log.info(u"File transfer declined") - self._si.sendError(client, iq_elt, 'forbidden') + self._si.sendError(client, iq_elt, "forbidden") return # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid] # can_range = data['can_range'] == "True" @@ -207,12 +238,16 @@ class XEP_0096(object): # file_obj = self._getFileObject(dest_path, can_range) # range_offset = file_obj.tell() - d = data['stream_plugin'].createSession(client, data['stream_object'], data['peer_jid'], data['si_id']) + d = data["stream_plugin"].createSession( + client, data["stream_object"], data["peer_jid"], data["si_id"] + ) d.addCallback(self._transferCb, client, data) d.addErrback(self._transferEb, client, data) - #we can send the iq result - feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': data['stream_method']}, namespace=None) + # we can send the iq result + feature_elt = self.host.plugins["XEP-0020"].chooseOption( + {"stream-method": data["stream_method"]}, namespace=None + ) misc_elts = [] misc_elts.append(domish.Element((SI_PROFILE, "file"))) # if can_range: @@ -227,9 +262,9 @@ class XEP_0096(object): @param data: session data """ - #TODO: check hash - data['stream_object'].close() - log.info(u'Transfer {si_id} successfuly finished'.format(**data)) + # TODO: check hash + data["stream_object"].close() + log.info(u"Transfer {si_id} successfuly finished".format(**data)) def _transferEb(self, failure, client, data): """Called when something went wrong with the transfer @@ -237,12 +272,18 @@ class XEP_0096(object): @param id: stream id @param data: session data """ - log.warning(u'Transfer {si_id} failed: {reason}'.format(reason=unicode(failure.value), **data)) - data['stream_object'].close() + log.warning( + u"Transfer {si_id} failed: {reason}".format( + reason=unicode(failure.value), **data + ) + ) + data["stream_object"].close() def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE): client = self.host.getClient(profile) - return self.sendFile(client, jid.JID(peer_jid_s), filepath, name or None, desc or None) + return self.sendFile( + client, jid.JID(peer_jid_s), filepath, name or None, desc or None + ) def sendFile(self, client, peer_jid, filepath, name=None, desc=None, extra=None): """Send a file using XEP-0096 @@ -255,23 +296,27 @@ class XEP_0096(object): @param extra: not used here @return: an unique id to identify the transfer """ - feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}, namespace=None) + feature_elt = self.host.plugins["XEP-0020"].proposeFeatures( + {"stream-method": self.managed_stream_m}, namespace=None + ) file_transfer_elts = [] statinfo = os.stat(filepath) - file_elt = domish.Element((SI_PROFILE, 'file')) - file_elt['name'] = name or os.path.basename(filepath) - assert '/' not in file_elt['name'] + file_elt = domish.Element((SI_PROFILE, "file")) + file_elt["name"] = name or os.path.basename(filepath) + assert "/" not in file_elt["name"] size = statinfo.st_size - file_elt['size'] = str(size) + file_elt["size"] = str(size) if desc: - file_elt.addElement('desc', content=desc) + file_elt.addElement("desc", content=desc) file_transfer_elts.append(file_elt) - file_transfer_elts.append(domish.Element((None, 'range'))) + file_transfer_elts.append(domish.Element((None, "range"))) - sid, offer_d = self._si.proposeStream(client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts) + sid, offer_d = self._si.proposeStream( + client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts + ) args = [filepath, sid, size, client] offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args) return sid @@ -285,7 +330,9 @@ class XEP_0096(object): log.warning(u"No element found in result while expected") return - choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elt, namespace=None) + choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions( + feature_elt, namespace=None + ) try: stream_method = choosed_options["stream-method"] except KeyError: @@ -307,45 +354,46 @@ class XEP_0096(object): log.warning(u"Invalid stream method received") return - stream_object = stream.FileStreamObject(self.host, - client, - filepath, - uid=sid, - size=size, - ) - d = plugin.startStream(client, stream_object, jid.JID(iq_elt['from']), sid) + stream_object = stream.FileStreamObject( + self.host, client, filepath, uid=sid, size=size + ) + d = plugin.startStream(client, stream_object, jid.JID(iq_elt["from"]), sid) d.addCallback(self._sendCb, client, sid, stream_object) d.addErrback(self._sendEb, client, sid, stream_object) def _fileEb(self, failure, filepath, sid, size, client): if failure.check(error.StanzaError): stanza_err = failure.value - if stanza_err.code == '403' and stanza_err.condition == 'forbidden': - from_s = stanza_err.stanza['from'] + if stanza_err.code == "403" and stanza_err.condition == "forbidden": + from_s = stanza_err.stanza["from"] log.info(u"File transfer refused by {}".format(from_s)) msg = D_(u"The contact {} has refused your file").format(from_s) title = D_(u"File refused") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) else: log.warning(_(u"Error during file transfer")) - msg = D_(u"Something went wrong during the file transfer session initialisation: {reason}").format(reason=unicode(stanza_err)) + msg = D_( + u"Something went wrong during the file transfer session initialisation: {reason}" + ).format(reason=unicode(stanza_err)) title = D_(u"File transfer error") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR) elif failure.check(exceptions.DataError): - log.warning(u'Invalid stanza received') + log.warning(u"Invalid stanza received") else: - log.error(u'Error while proposing stream: {}'.format(failure)) + log.error(u"Error while proposing stream: {}".format(failure)) def _sendCb(self, dummy, client, sid, stream_object): - log.info(_(u'transfer {sid} successfuly finished [{profile}]').format( - sid=sid, - profile=client.profile)) + log.info( + _(u"transfer {sid} successfuly finished [{profile}]").format( + sid=sid, profile=client.profile + ) + ) stream_object.close() def _sendEb(self, failure, client, sid, stream_object): - log.warning(_(u'transfer {sid} failed [{profile}]: {reason}').format( - sid=sid, - profile=client.profile, - reason=unicode(failure.value), - )) + log.warning( + _(u"transfer {sid} failed [{profile}]: {reason}").format( + sid=sid, profile=client.profile, reason=unicode(failure.value) + ) + ) stream_object.close() diff --git a/sat/plugins/plugin_xep_0100.py b/sat/plugins/plugin_xep_0100.py index 0c64297d..73d9ba06 100755 --- a/sat/plugins/plugin_xep_0100.py +++ b/sat/plugins/plugin_xep_0100.py @@ -22,6 +22,7 @@ from sat.core.constants import Const as C from sat.core import exceptions from sat.tools import xml_tools from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.internet import reactor, defer @@ -33,37 +34,58 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0100"], C.PI_DEPENDENCIES: ["XEP-0077"], C.PI_MAIN: "XEP_0100", - C.PI_DESCRIPTION: _("""Implementation of Gateways protocol""") + C.PI_DESCRIPTION: _("""Implementation of Gateways protocol"""), } -WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts. -But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""") +WARNING_MSG = D_( + u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts. +But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""" +) GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds -TYPE_DESCRIPTIONS = {'irc': D_("Internet Relay Chat"), - 'xmpp': D_("XMPP"), - 'qq': D_("Tencent QQ"), - 'simple': D_("SIP/SIMPLE"), - 'icq': D_("ICQ"), - 'yahoo': D_("Yahoo! Messenger"), - 'gadu-gadu': D_("Gadu-Gadu"), - 'aim': D_("AOL Instant Messenger"), - 'msn': D_("Windows Live Messenger"), - } +TYPE_DESCRIPTIONS = { + "irc": D_("Internet Relay Chat"), + "xmpp": D_("XMPP"), + "qq": D_("Tencent QQ"), + "simple": D_("SIP/SIMPLE"), + "icq": D_("ICQ"), + "yahoo": D_("Yahoo! Messenger"), + "gadu-gadu": D_("Gadu-Gadu"), + "aim": D_("AOL Instant Messenger"), + "msn": D_("Windows Live Messenger"), +} class XEP_0100(object): - def __init__(self, host): log.info(_("Gateways plugin initialization")) self.host = host self.__gateways = {} # dict used to construct the answer to findGateways. Key = target jid - host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways) - host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister) + host.bridge.addMethod( + "findGateways", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._findGateways, + ) + host.bridge.addMethod( + "gatewayRegister", + ".plugin", + in_sign="ss", + out_sign="s", + method=self._gatewayRegister, + ) self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True) - self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True) - host.importMenu((D_("Service"), D_("Gateways")), self._gatewaysMenu, security_limit=1, help_string=D_("Find gateways")) + self.__selected_id = host.registerCallback( + self._gatewaySelectedCb, with_data=True + ) + host.importMenu( + (D_("Service"), D_("Gateways")), + self._gatewaysMenu, + security_limit=1, + help_string=D_("Find gateways"), + ) def _gatewaysMenu(self, data, profile): """ XMLUI activated by menu: return Gateways UI @@ -72,24 +94,31 @@ class XEP_0100(object): """ client = self.host.getClient(profile) try: - jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host)) + jid_ = jid.JID( + data.get(xml_tools.formEscape("external_jid"), client.jid.host) + ) except RuntimeError: raise exceptions.DataError(_("Invalid JID")) d = self.findGateways(jid_, profile) d.addCallback(self._gatewaysResult2XMLUI, jid_) - d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()}) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) return d def _gatewaysResult2XMLUI(self, result, entity): - xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full()) + xmlui = xml_tools.XMLUI(title=_("Gateways manager (%s)") % entity.full()) xmlui.addText(_(WARNING_MSG)) - xmlui.addDivider('dash') - adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id) + xmlui.addDivider("dash") + adv_list = xmlui.changeContainer( + "advanced_list", + columns=3, + selectable="single", + callback_id=self.__selected_id, + ) for success, gateway_data in result: if not success: fail_cond, disco_item = gateway_data xmlui.addJid(disco_item.entity) - xmlui.addText(_('Failed (%s)') % fail_cond) + xmlui.addText(_("Failed (%s)") % fail_cond) xmlui.addEmpty() else: jid_, data = gateway_data @@ -100,22 +129,22 @@ class XEP_0100(object): xmlui.addText(name) xmlui.addText(self._getIdentityDesc(identity)) adv_list.end() - xmlui.addDivider('blank') - xmlui.changeContainer('advanced_list', columns=3) - xmlui.addLabel(_('Use external XMPP server')) - xmlui.addString('external_jid') - xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',)) + xmlui.addDivider("blank") + xmlui.changeContainer("advanced_list", columns=3) + xmlui.addLabel(_("Use external XMPP server")) + xmlui.addString("external_jid") + xmlui.addButton(self.__menu_id, _(u"Go !"), fields_back=("external_jid",)) return xmlui def _gatewaySelectedCb(self, data, profile): try: - target_jid = jid.JID(data['index']) + target_jid = jid.JID(data["index"]) except (KeyError, RuntimeError): log.warning(_("No gateway index selected")) return {} d = self.gatewayRegister(target_jid, profile) - d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()}) + d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()}) return d def _getIdentityDesc(self, identity): @@ -124,8 +153,13 @@ class XEP_0100(object): """ category, type_ = identity - if category != 'gateway': - log.error(_(u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category) + if category != "gateway": + log.error( + _( + u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"' + ) + % category + ) try: return _(TYPE_DESCRIPTIONS[type_]) except KeyError: @@ -145,8 +179,10 @@ class XEP_0100(object): def gatewayRegister(self, target_jid, profile_key=C.PROF_KEY_NONE): """Register gateway using in-band registration, then log-in to gateway""" profile = self.host.memory.getProfileName(profile_key) - assert(profile) - d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile) + assert profile + d = self.host.plugins["XEP-0077"].inBandRegister( + target_jid, self._registrationSuccessful, profile + ) return d def _infosReceived(self, dl_result, items, target, client): @@ -165,12 +201,27 @@ class XEP_0100(object): ret.append((success, (msg, items[idx]))) else: entity = items[idx].entity - gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway'] + gateways = [ + (identity, result.identities[identity]) + for identity in result.identities + if identity[0] == "gateway" + ] if gateways: - log.info(_(u"Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])}) + log.info( + _(u"Found gateway [%(jid)s]: %(identity_name)s") + % { + "jid": entity.full(), + "identity_name": " - ".join( + [gateway[1] for gateway in gateways] + ), + } + ) ret.append((success, (entity, gateways))) else: - log.info(_(u"Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()}) + log.info( + _(u"Skipping [%(jid)s] which is not a gateway") + % {"jid": entity.full()} + ) return ret def _itemsReceived(self, disco, target, client): @@ -185,7 +236,9 @@ class XEP_0100(object): log.debug(_(u"item found: %s") % item.entity) _defers.append(client.disco.requestInfo(item.entity)) dl = defer.DeferredList(_defers) - dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client) + dl.addCallback( + self._infosReceived, items=disco._items, target=target, client=client + ) reactor.callLater(GATEWAY_TIMEOUT, dl.cancel) return dl @@ -203,7 +256,10 @@ class XEP_0100(object): """Find gateways in the target JID, using discovery protocol """ client = self.host.getClient(profile) - log.debug(_(u"find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile}) + log.debug( + _(u"find gateways (target = %(target)s, profile = %(profile)s)") + % {"target": target.full(), "profile": profile} + ) d = client.disco.requestItems(target) d.addCallback(self._itemsReceived, target=target, client=client) return d diff --git a/sat/plugins/plugin_xep_0115.py b/sat/plugins/plugin_xep_0115.py index 59589815..c900e46f 100755 --- a/sat/plugins/plugin_xep_0115.py +++ b/sat/plugins/plugin_xep_0115.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import jid @@ -32,9 +33,9 @@ try: except ImportError: from wokkel.subprotocols import XMPPHandler -PRESENCE = '/presence' -NS_ENTITY_CAPABILITY = 'http://jabber.org/protocol/caps' -NS_CAPS_OPTIMIZE = 'http://jabber.org/protocol/caps#optimize' +PRESENCE = "/presence" +NS_ENTITY_CAPABILITY = "http://jabber.org/protocol/caps" +NS_CAPS_OPTIMIZE = "http://jabber.org/protocol/caps#optimize" CAPABILITY_UPDATE = PRESENCE + '/c[@xmlns="' + NS_ENTITY_CAPABILITY + '"]' PLUGIN_INFO = { @@ -46,7 +47,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: "XEP_0115", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of entity capabilities""") + C.PI_DESCRIPTION: _("""Implementation of entity capabilities"""), } @@ -75,24 +76,29 @@ class XEP_0115(object): log.warning(_(u"Caps optimisation not available")) # hash generation - _infos = yield client.discoHandler.info(client.jid, client.jid, '') + _infos = yield client.discoHandler.info(client.jid, client.jid, "") disco_infos = disco.DiscoInfo() for item in _infos: disco_infos.append(item) cap_hash = client._caps_hash = self.host.memory.disco.generateHash(disco_infos) - log.info(u"Our capability hash has been generated: [{cap_hash}]".format( - cap_hash = cap_hash)) + log.info( + u"Our capability hash has been generated: [{cap_hash}]".format( + cap_hash=cap_hash + ) + ) log.debug(u"Generating capability domish.Element") - c_elt = domish.Element((NS_ENTITY_CAPABILITY, 'c')) - c_elt['hash'] = 'sha-1' - c_elt['node'] = C.APP_URL - c_elt['ver'] = cap_hash + c_elt = domish.Element((NS_ENTITY_CAPABILITY, "c")) + c_elt["hash"] = "sha-1" + c_elt["node"] = C.APP_URL + c_elt["ver"] = cap_hash client._caps_elt = c_elt if client._caps_optimize: client._caps_sent = False if cap_hash not in self.host.memory.disco.hashes: self.host.memory.disco.hashes[cap_hash] = disco_infos - self.host.memory.updateEntityData(client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile) + self.host.memory.updateEntityData( + client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile + ) def _presenceAddElt(self, client, obj): if client._caps_optimize: @@ -120,10 +126,13 @@ class XEP_0115_handler(XMPPHandler): def connectionInitialized(self): self.xmlstream.addObserver(CAPABILITY_UPDATE, self.update) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - return [disco.DiscoFeature(NS_ENTITY_CAPABILITY), disco.DiscoFeature(NS_CAPS_OPTIMIZE)] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + return [ + disco.DiscoFeature(NS_ENTITY_CAPABILITY), + disco.DiscoFeature(NS_CAPS_OPTIMIZE), + ] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] def update(self, presence): @@ -132,35 +141,65 @@ class XEP_0115_handler(XMPPHandler): Check if we know the version of this capabilities and get the capabilities if necessary """ - from_jid = jid.JID(presence['from']) - c_elem = presence.elements(NS_ENTITY_CAPABILITY, 'c').next() + from_jid = jid.JID(presence["from"]) + c_elem = presence.elements(NS_ENTITY_CAPABILITY, "c").next() try: - c_ver = c_elem['ver'] - c_hash = c_elem['hash'] - c_node = c_elem['node'] + c_ver = c_elem["ver"] + c_hash = c_elem["hash"] + c_node = c_elem["node"] except KeyError: - log.warning(_(u'Received invalid capabilities tag: %s') % c_elem.toXml()) + log.warning(_(u"Received invalid capabilities tag: %s") % c_elem.toXml()) return if c_ver in self.host.memory.disco.hashes: # we already know the hash, we update the jid entity - log.debug(u"hash [%(hash)s] already in cache, updating entity [%(jid)s]" % {'hash': c_ver, 'jid': from_jid.full()}) - self.host.memory.updateEntityData(from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile) + log.debug( + u"hash [%(hash)s] already in cache, updating entity [%(jid)s]" + % {"hash": c_ver, "jid": from_jid.full()} + ) + self.host.memory.updateEntityData( + from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile + ) return - if c_hash != 'sha-1': # unknown hash method - log.warning(_(u'Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)') % {'hash_method':c_hash, 'jid': from_jid, 'node': c_node}) + if c_hash != "sha-1": # unknown hash method + log.warning( + _( + u"Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)" + ) + % {"hash_method": c_hash, "jid": from_jid, "node": c_node} + ) def cb(dummy): - computed_hash = self.host.memory.getEntityDatum(from_jid, C.ENTITY_CAP_HASH, self.profile) + computed_hash = self.host.memory.getEntityDatum( + from_jid, C.ENTITY_CAP_HASH, self.profile + ) if computed_hash != c_ver: - log.warning(_(u'Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)') % {'given_hash':c_ver, 'computed_hash': computed_hash, 'jid': from_jid, 'node': c_node}) + log.warning( + _( + u"Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)" + ) + % { + "given_hash": c_ver, + "computed_hash": computed_hash, + "jid": from_jid, + "node": c_node, + } + ) def eb(failure): if isinstance(failure.value, error.ConnectionDone): return - msg = failure.value.condition if hasattr(failure.value, 'condition') else failure.getErrorMessage() - log.error(_(u"Couldn't retrieve disco info for {jid}: {error}").format(jid=from_jid.full(), error=msg)) + msg = ( + failure.value.condition + if hasattr(failure.value, "condition") + else failure.getErrorMessage() + ) + log.error( + _(u"Couldn't retrieve disco info for {jid}: {error}").format( + jid=from_jid.full(), error=msg + ) + ) d = self.host.getDiscoInfos(self.parent, from_jid) d.addCallbacks(cb, eb) diff --git a/sat/plugins/plugin_xep_0163.py b/sat/plugins/plugin_xep_0163.py index e1d281e8..63a3b12a 100755 --- a/sat/plugins/plugin_xep_0163.py +++ b/sat/plugins/plugin_xep_0163.py @@ -21,13 +21,14 @@ from sat.core.i18n import _ from sat.core import exceptions from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.xish import domish from wokkel import disco, pubsub from wokkel.formats import Mood -NS_USER_MOOD = 'http://jabber.org/protocol/mood' +NS_USER_MOOD = "http://jabber.org/protocol/mood" PLUGIN_INFO = { C.PI_NAME: "Personal Eventing Protocol Plugin", @@ -37,19 +38,25 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0060"], C.PI_MAIN: "XEP_0163", C.PI_HANDLER: "no", - C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol""") + C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol"""), } class XEP_0163(object): - def __init__(self, host): log.info(_("PEP plugin initialization")) self.host = host self.pep_events = set() self.pep_out_cb = {} host.trigger.add("PubSub Disco Info", self.disoInfoTrigger) - host.bridge.addMethod("PEPSend", ".plugin", in_sign='sa{ss}s', out_sign='', method=self.PEPSend, async=True) # args: type(MOOD, TUNE, etc), data, profile_key; + host.bridge.addMethod( + "PEPSend", + ".plugin", + in_sign="sa{ss}s", + out_sign="", + method=self.PEPSend, + async=True, + ) # args: type(MOOD, TUNE, etc), data, profile_key; self.addPEPEvent("MOOD", NS_USER_MOOD, self.userMoodCB, self.sendMood) def disoInfoTrigger(self, disco_info, profile): @@ -78,13 +85,18 @@ class XEP_0163(object): self.pep_events.add(node) if notify: self.pep_events.add(node + "+notify") + def filterPEPEvent(client, itemsEvent): """Ignore messages which are not coming from PEP (i.e. main server) @param itemsEvent(pubsub.ItemsEvent): pubsub event """ if itemsEvent.sender.user or itemsEvent.sender.resource: - log.debug("ignoring non PEP event from {} (profile={})".format(itemsEvent.sender.full(), client.profile)) + log.debug( + "ignoring non PEP event from {} (profile={})".format( + itemsEvent.sender.full(), client.profile + ) + ) return in_callback(itemsEvent, client.profile) @@ -110,11 +122,14 @@ class XEP_0163(object): """ profile = self.host.memory.getProfileName(profile_key) if not profile: - log.error(_(u'Trying to send personal event with an unknown profile key [%s]') % profile_key) + log.error( + _(u"Trying to send personal event with an unknown profile key [%s]") + % profile_key + ) raise exceptions.ProfileUnknownError if not event_type in self.pep_out_cb.keys(): - log.error(_('Trying to send personal event for an unknown type')) - raise exceptions.DataError('Type unknown') + log.error(_("Trying to send personal event for an unknown type")) + raise exceptions.DataError("Type unknown") return self.pep_out_cb[event_type](data, profile) def userMoodCB(self, itemsEvent, profile): @@ -122,7 +137,9 @@ class XEP_0163(object): log.debug(_("No item found")) return try: - mood_elt = [child for child in itemsEvent.items[0].elements() if child.name == "mood"][0] + mood_elt = [ + child for child in itemsEvent.items[0].elements() if child.name == "mood" + ][0] except IndexError: log.error(_("Can't find mood element in mood event")) return @@ -130,8 +147,14 @@ class XEP_0163(object): if not mood: log.debug(_("No mood found")) return - self.host.bridge.psEvent(C.PS_PEP, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, - "MOOD", {"mood": mood.value or "", "text": mood.text or ""}, profile) + self.host.bridge.psEvent( + C.PS_PEP, + itemsEvent.sender.full(), + itemsEvent.nodeIdentifier, + "MOOD", + {"mood": mood.value or "", "text": mood.text or ""}, + profile, + ) def sendMood(self, data, profile): """Send XEP-0107's User Mood @@ -139,8 +162,8 @@ class XEP_0163(object): @param data: must include mood and text @param profile: profile which send the mood""" try: - value = data['mood'].lower() - text = data['text'] if 'text' in data else '' + value = data["mood"].lower() + text = data["text"] if "text" in data else "" except KeyError: raise exceptions.DataError("Mood data must contain at least 'mood' key") mood = UserMood(value, text) @@ -152,7 +175,7 @@ class UserMood(Mood, domish.Element): def __init__(self, value, text=None): Mood.__init__(self, value, text) - domish.Element.__init__(self, (NS_USER_MOOD, 'mood')) + domish.Element.__init__(self, (NS_USER_MOOD, "mood")) self.addElement(value) if text: - self.addElement('text', content=text) + self.addElement("text", content=text) diff --git a/sat/plugins/plugin_xep_0166.py b/sat/plugins/plugin_xep_0166.py index be7a135f..cae1c6bf 100644 --- a/sat/plugins/plugin_xep_0166.py +++ b/sat/plugins/plugin_xep_0166.py @@ -21,6 +21,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core.log import getLogger from sat.tools import xml_tools + log = getLogger(__name__) from sat.core import exceptions from twisted.words.protocols.jabber import jid @@ -37,7 +38,6 @@ import time from zope.interface import implements - IQ_SET = '/iq[@type="set"]' NS_JINGLE = "urn:xmpp:jingle:1" NS_JINGLE_ERROR = "urn:xmpp:jingle:errors:1" @@ -55,24 +55,24 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0166"], C.PI_MAIN: "XEP_0166", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle""") + C.PI_DESCRIPTION: _("""Implementation of Jingle"""), } -ApplicationData = namedtuple('ApplicationData', ('namespace', 'handler')) -TransportData = namedtuple('TransportData', ('namespace', 'handler', 'priority')) +ApplicationData = namedtuple("ApplicationData", ("namespace", "handler")) +TransportData = namedtuple("TransportData", ("namespace", "handler", "priority")) class XEP_0166(object): ROLE_INITIATOR = "initiator" ROLE_RESPONDER = "responder" - TRANSPORT_DATAGRAM='UDP' - TRANSPORT_STREAMING='TCP' - REASON_SUCCESS='success' - REASON_DECLINE='decline' - REASON_FAILED_APPLICATION='failed-application' - REASON_FAILED_TRANSPORT='failed-transport' - REASON_CONNECTIVITY_ERROR='connectivity-error' + TRANSPORT_DATAGRAM = "UDP" + TRANSPORT_STREAMING = "TCP" + REASON_SUCCESS = "success" + REASON_DECLINE = "decline" + REASON_FAILED_APPLICATION = "failed-application" + REASON_FAILED_TRANSPORT = "failed-transport" + REASON_CONNECTIVITY_ERROR = "connectivity-error" A_SESSION_INITIATE = "session-initiate" A_SESSION_ACCEPT = "session-accept" A_SESSION_TERMINATE = "session-terminate" @@ -82,21 +82,26 @@ class XEP_0166(object): A_TRANSPORT_REJECT = "transport-reject" A_TRANSPORT_INFO = "transport-info" # non standard actions - A_PREPARE_INITIATOR = "prepare-initiator" # initiator must prepare tranfer - A_PREPARE_RESPONDER = "prepare-responder" # responder must prepare tranfer - A_ACCEPTED_ACK = "accepted-ack" # session accepted ack has been received from initiator - A_START = "start" # application can start - A_DESTROY = "destroy" # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations + A_PREPARE_INITIATOR = "prepare-initiator" # initiator must prepare tranfer + A_PREPARE_RESPONDER = "prepare-responder" # responder must prepare tranfer + A_ACCEPTED_ACK = ( + "accepted-ack" + ) # session accepted ack has been received from initiator + A_START = "start" # application can start + A_DESTROY = ( + "destroy" + ) # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations def __init__(self, host): log.info(_("plugin Jingle initialization")) self.host = host - self._applications = {} # key: namespace, value: application data - self._transports = {} # key: namespace, value: transport data + self._applications = {} # key: namespace, value: application data + self._transports = {} # key: namespace, value: transport data # we also keep transports by type, they are then sorted by priority - self._type_transports = { XEP_0166.TRANSPORT_DATAGRAM: [], - XEP_0166.TRANSPORT_STREAMING: [], - } + self._type_transports = { + XEP_0166.TRANSPORT_DATAGRAM: [], + XEP_0166.TRANSPORT_STREAMING: [], + } def profileConnected(self, client): client.jingle_sessions = {} # key = sid, value = session_data @@ -115,12 +120,12 @@ class XEP_0166(object): ## helpers methods to build stanzas ## def _buildJingleElt(self, client, session, action): - iq_elt = client.IQ('set') - iq_elt['from'] = client.jid.full() - iq_elt['to'] = session['peer_jid'].full() + iq_elt = client.IQ("set") + iq_elt["from"] = client.jid.full() + iq_elt["to"] = session["peer_jid"].full() jingle_elt = iq_elt.addElement("jingle", NS_JINGLE) - jingle_elt["sid"] = session['id'] - jingle_elt['action'] = action + jingle_elt["sid"] = session["id"] + jingle_elt["action"] = action return iq_elt, jingle_elt def sendError(self, client, error_condition, sid, request, jingle_condition=None): @@ -134,9 +139,13 @@ class XEP_0166(object): iq_elt = error.StanzaError(error_condition).toResponse(request) if jingle_condition is not None: iq_elt.error.addElement((NS_JINGLE_ERROR, jingle_condition)) - if error.STANZA_CONDITIONS[error_condition]['type'] == 'cancel' and sid: + if error.STANZA_CONDITIONS[error_condition]["type"] == "cancel" and sid: self._delSession(client, sid) - log.warning(u"Error while managing jingle session, cancelling: {condition}".format(error_condition)) + log.warning( + u"Error while managing jingle session, cancelling: {condition}".format( + error_condition + ) + ) client.send(iq_elt) def _terminateEb(self, failure_): @@ -150,14 +159,16 @@ class XEP_0166(object): if a list of element, add them as children of the element @param session(dict): data of the session """ - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_TERMINATE) - reason_elt = jingle_elt.addElement('reason') + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_TERMINATE + ) + reason_elt = jingle_elt.addElement("reason") if isinstance(reason, basestring): reason_elt.addElement(reason) else: for elt in reason: reason_elt.addChild(elt) - self._delSession(client, session['id']) + self._delSession(client, session["id"]) d = iq_elt.send() d.addErrback(self._terminateEb) return d @@ -170,7 +181,11 @@ class XEP_0166(object): @param failure_(failure.Failure): the exceptions raised @param sid(unicode): jingle session id """ - log.warning(u"Error while sending jingle stanza: {failure_}".format(failure_=failure_.value)) + log.warning( + u"Error while sending jingle stanza: {failure_}".format( + failure_=failure_.value + ) + ) self._delSession(client, sid) def _jingleErrorCb(self, fail, sid, request, client): @@ -185,7 +200,7 @@ class XEP_0166(object): """ log.warning("Error while processing jingle request") if isinstance(fail, exceptions.DataError): - self.sendError(client, 'bad-request', sid, request) + self.sendError(client, "bad-request", sid, request) else: log.error("Unmanaged jingle exception") self._delSession(client, sid) @@ -211,8 +226,12 @@ class XEP_0166(object): May be used to clean session """ if namespace in self._applications: - raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace)) - self._applications[namespace] = ApplicationData(namespace=namespace, handler=handler) + raise exceptions.ConflictError( + u"Trying to register already registered namespace {}".format(namespace) + ) + self._applications[namespace] = ApplicationData( + namespace=namespace, handler=handler + ) log.debug(u"new jingle application registered") def registerTransport(self, namespace, transport_type, handler, priority=0): @@ -227,12 +246,21 @@ class XEP_0166(object): called on several action to negociate the application or transport @param priority(int): priority of this transport """ - assert transport_type in (XEP_0166.TRANSPORT_DATAGRAM, XEP_0166.TRANSPORT_STREAMING) + assert transport_type in ( + XEP_0166.TRANSPORT_DATAGRAM, + XEP_0166.TRANSPORT_STREAMING, + ) if namespace in self._transports: - raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace)) - transport_data = TransportData(namespace=namespace, handler=handler, priority=priority) + raise exceptions.ConflictError( + u"Trying to register already registered namespace {}".format(namespace) + ) + transport_data = TransportData( + namespace=namespace, handler=handler, priority=priority + ) self._type_transports[transport_type].append(transport_data) - self._type_transports[transport_type].sort(key=lambda transport_data: transport_data.priority, reverse=True) + self._type_transports[transport_type].sort( + key=lambda transport_data: transport_data.priority, reverse=True + ) self._transports[namespace] = transport_data log.debug(u"new jingle transport registered") @@ -247,20 +275,24 @@ class XEP_0166(object): # XXX: for now we replace the transport before receiving confirmation from other peer # this is acceptable because we terminate the session if transport is rejected. # this behavious may change in the future. - content_data= session['contents'][content_name] - transport_data = content_data['transport_data'] + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] try: transport = self._transports[transport_ns] except KeyError: raise exceptions.InternalError(u"Unkown transport") - yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None) - content_data['transport'] = transport + yield content_data["transport"].handler.jingleHandler( + client, XEP_0166.A_DESTROY, session, content_name, None + ) + content_data["transport"] = transport transport_data.clear() - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REPLACE) - content_elt = jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_REPLACE + ) + content_elt = jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] transport_elt = transport.handler.jingleSessionInit(client, session, content_name) content_elt.addChild(transport_elt) @@ -279,14 +311,16 @@ class XEP_0166(object): # we first build iq, jingle and content element which are the same in every cases iq_elt, jingle_elt = self._buildJingleElt(client, session, action) # FIXME: XEP-0260 § 2.3 Ex 5 has an initiator attribute, but it should not according to XEP-0166 §7.1 table 1, must be checked - content_data= session['contents'][content_name] - content_elt = jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + content_data = session["contents"][content_name] + content_elt = jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] if action == XEP_0166.A_TRANSPORT_INFO: - context_elt = transport_elt = content_elt.addElement('transport', content_data['transport'].namespace) - transport_elt['sid'] = content_data['transport_data']['sid'] + context_elt = transport_elt = content_elt.addElement( + "transport", content_data["transport"].namespace + ) + transport_elt["sid"] = content_data["transport_data"]["sid"] else: raise exceptions.InternalError(u"unmanaged action {}".format(action)) @@ -298,7 +332,7 @@ class XEP_0166(object): @param session(dict): jingle session data @return (tuple[domish.Element, domish.Element]): parent element, element """ - return self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO) + return self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO) @defer.inlineCallbacks def initiate(self, client, peer_jid, contents): @@ -318,74 +352,88 @@ class XEP_0166(object): - app_kwargs(dict): keyword args to pass to the application plugin @return D(unicode): jingle session id """ - assert contents # there must be at least one content + assert contents # there must be at least one content if peer_jid == client.jid: raise ValueError(_(u"You can't do a jingle session with yourself")) initiator = client.jid sid = unicode(uuid.uuid4()) # TODO: session cleaning after timeout ? - session = client.jingle_sessions[sid] = {'id': sid, - 'state': STATE_PENDING, - 'initiator': initiator, - 'role': XEP_0166.ROLE_INITIATOR, - 'peer_jid': peer_jid, - 'started': time.time(), - 'contents': {} - } - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_INITIATE) + session = client.jingle_sessions[sid] = { + "id": sid, + "state": STATE_PENDING, + "initiator": initiator, + "role": XEP_0166.ROLE_INITIATOR, + "peer_jid": peer_jid, + "started": time.time(), + "contents": {}, + } + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_INITIATE + ) jingle_elt["initiator"] = initiator.full() - contents_dict = session['contents'] + contents_dict = session["contents"] for content in contents: # we get the application plugin - app_ns = content['app_ns'] + app_ns = content["app_ns"] try: application = self._applications[app_ns] except KeyError: - raise exceptions.InternalError(u"No application registered for {}".format(app_ns)) + raise exceptions.InternalError( + u"No application registered for {}".format(app_ns) + ) # and the transport plugin - transport_type = content.get('transport_type', XEP_0166.TRANSPORT_STREAMING) + transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING) try: transport = self._type_transports[transport_type][0] except IndexError: - raise exceptions.InternalError(u"No transport registered for {}".format(transport_type)) + raise exceptions.InternalError( + u"No transport registered for {}".format(transport_type) + ) # we build the session data - content_data = {'application': application, - 'application_data': {}, - 'transport': transport, - 'transport_data': {}, - 'creator': XEP_0166.ROLE_INITIATOR, - 'senders': content.get('senders', 'both'), - } + content_data = { + "application": application, + "application_data": {}, + "transport": transport, + "transport_data": {}, + "creator": XEP_0166.ROLE_INITIATOR, + "senders": content.get("senders", "both"), + } try: - content_name = content['name'] + content_name = content["name"] except KeyError: content_name = unicode(uuid.uuid4()) else: if content_name in contents_dict: - raise exceptions.InternalError('There is already a content with this name') + raise exceptions.InternalError( + "There is already a content with this name" + ) contents_dict[content_name] = content_data # we construct the content element - content_elt = jingle_elt.addElement('content') - content_elt['creator'] = content_data['creator'] - content_elt['name'] = content_name + content_elt = jingle_elt.addElement("content") + content_elt["creator"] = content_data["creator"] + content_elt["name"] = content_name try: - content_elt['senders'] = content['senders'] + content_elt["senders"] = content["senders"] except KeyError: pass # then the description element - app_args = content.get('app_args', []) - app_kwargs = content.get('app_kwargs', {}) - desc_elt = yield application.handler.jingleSessionInit(client, session, content_name, *app_args, **app_kwargs) + app_args = content.get("app_args", []) + app_kwargs = content.get("app_kwargs", {}) + desc_elt = yield application.handler.jingleSessionInit( + client, session, content_name, *app_args, **app_kwargs + ) content_elt.addChild(desc_elt) # and the transport one - transport_elt = yield transport.handler.jingleSessionInit(client, session, content_name) + transport_elt = yield transport.handler.jingleSessionInit( + client, session, content_name + ) content_elt.addChild(transport_elt) try: @@ -410,17 +458,24 @@ class XEP_0166(object): @param content_name(unicode): name of the content terminated @param reason(unicode): reason of the termination """ - contents = session['contents'] + contents = session["contents"] del contents[content_name] if not contents: self.terminate(client, reason, session) ## defaults methods called when plugin doesn't have them ## - def jingleRequestConfirmationDefault(self, client, action, session, content_name, desc_elt): + def jingleRequestConfirmationDefault( + self, client, action, session, content_name, desc_elt + ): """This method request confirmation for a jingle session""" log.debug(u"Using generic jingle confirmation method") - return xml_tools.deferConfirm(self.host, _(CONFIRM_TXT).format(entity=session['peer_jid'].full()), _('Confirm Jingle session'), profile=client.profile) + return xml_tools.deferConfirm( + self.host, + _(CONFIRM_TXT).format(entity=session["peer_jid"].full()), + _("Confirm Jingle session"), + profile=client.profile, + ) ## jingle events ## @@ -432,29 +487,29 @@ class XEP_0166(object): @param request(domish.Element): received IQ request """ request.handled = True - jingle_elt = request.elements(NS_JINGLE, 'jingle').next() + jingle_elt = request.elements(NS_JINGLE, "jingle").next() # first we need the session id try: - sid = jingle_elt['sid'] + sid = jingle_elt["sid"] if not sid: raise KeyError except KeyError: log.warning(u"Received jingle request has no sid attribute") - self.sendError(client, 'bad-request', None, request) + self.sendError(client, "bad-request", None, request) return # then the action try: - action = jingle_elt['action'] + action = jingle_elt["action"] if not action: raise KeyError except KeyError: log.warning(u"Received jingle request has no action") - self.sendError(client, 'bad-request', None, request) + self.sendError(client, "bad-request", None, request) return - peer_jid = jid.JID(request['from']) + peer_jid = jid.JID(request["from"]) # we get or create the session try: @@ -463,32 +518,41 @@ class XEP_0166(object): if action == XEP_0166.A_SESSION_INITIATE: pass elif action == XEP_0166.A_SESSION_TERMINATE: - log.debug(u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format( - request_id=sid, - profile = client.profile)) + log.debug( + u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format( + request_id=sid, profile=client.profile + ) + ) return else: - log.warning(u"Received request for an unknown session id: {request_id} [{profile}]".format( - request_id=sid, - profile = client.profile)) - self.sendError(client, 'item-not-found', None, request, 'unknown-session') + log.warning( + u"Received request for an unknown session id: {request_id} [{profile}]".format( + request_id=sid, profile=client.profile + ) + ) + self.sendError(client, "item-not-found", None, request, "unknown-session") return - session = client.jingle_sessions[sid] = {'id': sid, - 'state': STATE_PENDING, - 'initiator': peer_jid, - 'role': XEP_0166.ROLE_RESPONDER, - 'peer_jid': peer_jid, - 'started': time.time(), - } + session = client.jingle_sessions[sid] = { + "id": sid, + "state": STATE_PENDING, + "initiator": peer_jid, + "role": XEP_0166.ROLE_RESPONDER, + "peer_jid": peer_jid, + "started": time.time(), + } else: - if session['peer_jid'] != peer_jid: - log.warning(u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format(sid)) - self.sendError(client, 'service-unavailable', sid, request) + if session["peer_jid"] != peer_jid: + log.warning( + u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format( + sid + ) + ) + self.sendError(client, "service-unavailable", sid, request) return - if session['id'] != sid: + if session["id"] != sid: log.error(u"session id doesn't match") - self.sendError(client, 'service-unavailable', sid, request) + self.sendError(client, "service-unavailable", sid, request) raise exceptions.InternalError if action == XEP_0166.A_SESSION_INITIATE: @@ -512,7 +576,17 @@ class XEP_0166(object): ## Actions callbacks ## - def _parseElements(self, jingle_elt, session, request, client, new=False, creator=ROLE_INITIATOR, with_application=True, with_transport=True): + def _parseElements( + self, + jingle_elt, + session, + request, + client, + new=False, + creator=ROLE_INITIATOR, + with_application=True, + with_transport=True, + ): """Parse contents elements and fill contents_dict accordingly after the parsing, contents_dict will containt handlers, "desc_elt" and "transport_elt" @@ -527,85 +601,98 @@ class XEP_0166(object): @param with_transport(bool): if True, raise an error if there is no element else ignore it @raise exceptions.CancelError: the error is treated and the calling method can cancel the treatment (i.e. return) """ - contents_dict = session['contents'] - content_elts = jingle_elt.elements(NS_JINGLE, 'content') + contents_dict = session["contents"] + content_elts = jingle_elt.elements(NS_JINGLE, "content") for content_elt in content_elts: - name = content_elt['name'] + name = content_elt["name"] if new: # the content must not exist, we check it if not name or name in contents_dict: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError - content_data = contents_dict[name] = {'creator': creator, - 'senders': content_elt.attributes.get('senders', 'both'), - } + content_data = contents_dict[name] = { + "creator": creator, + "senders": content_elt.attributes.get("senders", "both"), + } else: # the content must exist, we check it try: content_data = contents_dict[name] except KeyError: log.warning(u"Other peer try to access an unknown content") - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError # application if with_application: desc_elt = content_elt.description if not desc_elt: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError if new: # the content is new, we need to check and link the application app_ns = desc_elt.uri if not app_ns or app_ns == NS_JINGLE: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError try: application = self._applications[app_ns] except KeyError: - log.warning(u"Unmanaged application namespace [{}]".format(app_ns)) - self.sendError(client, 'service-unavailable', session['id'], request) + log.warning( + u"Unmanaged application namespace [{}]".format(app_ns) + ) + self.sendError( + client, "service-unavailable", session["id"], request + ) raise exceptions.CancelError - content_data['application'] = application - content_data['application_data'] = {} + content_data["application"] = application + content_data["application_data"] = {} else: # the content exists, we check that we have not a former desc_elt - if 'desc_elt' in content_data: - raise exceptions.InternalError(u"desc_elt should not exist at this point") + if "desc_elt" in content_data: + raise exceptions.InternalError( + u"desc_elt should not exist at this point" + ) - content_data['desc_elt'] = desc_elt + content_data["desc_elt"] = desc_elt # transport if with_transport: transport_elt = content_elt.transport if not transport_elt: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError if new: # the content is new, we need to check and link the transport transport_ns = transport_elt.uri if not app_ns or app_ns == NS_JINGLE: - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) raise exceptions.CancelError try: transport = self._transports[transport_ns] except KeyError: - raise exceptions.InternalError(u"No transport registered for namespace {}".format(transport_ns)) - content_data['transport'] = transport - content_data['transport_data'] = {} + raise exceptions.InternalError( + u"No transport registered for namespace {}".format( + transport_ns + ) + ) + content_data["transport"] = transport + content_data["transport_data"] = {} else: # the content exists, we check that we have not a former transport_elt - if 'transport_elt' in content_data: - raise exceptions.InternalError(u"transport_elt should not exist at this point") + if "transport_elt" in content_data: + raise exceptions.InternalError( + u"transport_elt should not exist at this point" + ) - content_data['transport_elt'] = transport_elt + content_data["transport_elt"] = transport_elt def _ignore(self, client, action, session, content_name, elt): """Dummy method used when not exception must be raised if a method is not implemented in _callPlugins @@ -614,10 +701,19 @@ class XEP_0166(object): """ return elt - def _callPlugins(self, client, action, session, app_method_name='jingleHandler', - transp_method_name='jingleHandler', - app_default_cb=None, transp_default_cb=None, delete=True, - elements=True, force_element=None): + def _callPlugins( + self, + client, + action, + session, + app_method_name="jingleHandler", + transp_method_name="jingleHandler", + app_default_cb=None, + transp_default_cb=None, + delete=True, + elements=True, + force_element=None, + ): """Call application and transport plugin methods for all contents @param action(unicode): jingle action name @@ -640,12 +736,13 @@ class XEP_0166(object): @return (list[defer.Deferred]): list of launched Deferred @raise exceptions.NotFound: method is not implemented """ - contents_dict = session['contents'] + contents_dict = session["contents"] defers_list = [] for content_name, content_data in contents_dict.iteritems(): for method_name, handler_key, default_cb, elt_name in ( - (app_method_name, 'application', app_default_cb, 'desc_elt'), - (transp_method_name, 'transport', transp_default_cb, 'transport_elt')): + (app_method_name, "application", app_default_cb, "desc_elt"), + (transp_method_name, "transport", transp_default_cb, "transport_elt"), + ): if method_name is None: continue @@ -654,14 +751,18 @@ class XEP_0166(object): method = getattr(handler, method_name) except AttributeError: if default_cb is None: - raise exceptions.NotFound(u'{} not implemented !'.format(method_name)) + raise exceptions.NotFound( + u"{} not implemented !".format(method_name) + ) else: method = default_cb if elements: elt = content_data.pop(elt_name) if delete else content_data[elt_name] else: elt = force_element - d = defer.maybeDeferred(method, client, action, session, content_name, elt) + d = defer.maybeDeferred( + method, client, action, session, content_name, elt + ) defers_list.append(d) return defers_list @@ -678,30 +779,42 @@ class XEP_0166(object): @param jingle_elt(domish.Element): element @param session(dict): session data """ - if 'contents' in session: - raise exceptions.InternalError("Contents dict should not already exist at this point") - session['contents'] = contents_dict = {} + if "contents" in session: + raise exceptions.InternalError( + "Contents dict should not already exist at this point" + ) + session["contents"] = contents_dict = {} try: - self._parseElements(jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR) + self._parseElements( + jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR + ) except exceptions.CancelError: return if not contents_dict: # there MUST be at least one content - self.sendError(client, 'bad-request', session['id'], request) + self.sendError(client, "bad-request", session["id"], request) return # at this point we can send the result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) # we now request each application plugin confirmation # and if all are accepted, we can accept the session - confirm_defers = self._callPlugins(client, XEP_0166.A_SESSION_INITIATE, session, 'jingleRequestConfirmation', None, self.jingleRequestConfirmationDefault, delete=False) + confirm_defers = self._callPlugins( + client, + XEP_0166.A_SESSION_INITIATE, + session, + "jingleRequestConfirmation", + None, + self.jingleRequestConfirmationDefault, + delete=False, + ) confirm_dlist = defer.gatherResults(confirm_defers) confirm_dlist.addCallback(self._confirmationCb, session, jingle_elt, client) - confirm_dlist.addErrback(self._jingleErrorCb, session['id'], request, client) + confirm_dlist.addErrback(self._jingleErrorCb, session["id"], request, client) def _confirmationCb(self, confirm_results, session, jingle_elt, client): """Method called when confirmation from user has been received @@ -716,8 +829,10 @@ class XEP_0166(object): if not confirmed: return self.terminate(client, XEP_0166.REASON_DECLINE, session) - iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_ACCEPT) - jingle_elt['responder'] = client.jid.full() + iq_elt, jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_SESSION_ACCEPT + ) + jingle_elt["responder"] = client.jid.full() # contents @@ -726,52 +841,87 @@ class XEP_0166(object): defers_list = [] - for content_name, content_data in session['contents'].iteritems(): - content_elt = jingle_elt.addElement('content') - content_elt['creator'] = XEP_0166.ROLE_INITIATOR - content_elt['name'] = content_name + for content_name, content_data in session["contents"].iteritems(): + content_elt = jingle_elt.addElement("content") + content_elt["creator"] = XEP_0166.ROLE_INITIATOR + content_elt["name"] = content_name - application = content_data['application'] + application = content_data["application"] app_session_accept_cb = application.handler.jingleHandler - app_d = defer.maybeDeferred(app_session_accept_cb, client, - XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('desc_elt')) + app_d = defer.maybeDeferred( + app_session_accept_cb, + client, + XEP_0166.A_SESSION_INITIATE, + session, + content_name, + content_data.pop("desc_elt"), + ) app_d.addCallback(addElement, content_elt) defers_list.append(app_d) - transport = content_data['transport'] + transport = content_data["transport"] transport_session_accept_cb = transport.handler.jingleHandler - transport_d = defer.maybeDeferred(transport_session_accept_cb, client, - XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('transport_elt')) + transport_d = defer.maybeDeferred( + transport_session_accept_cb, + client, + XEP_0166.A_SESSION_INITIATE, + session, + content_name, + content_data.pop("transport_elt"), + ) transport_d.addCallback(addElement, content_elt) defers_list.append(transport_d) d_list = defer.DeferredList(defers_list) - d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_PREPARE_RESPONDER, session, app_method_name=None, elements=False)) + d_list.addCallback( + lambda dummy: self._callPlugins( + client, + XEP_0166.A_PREPARE_RESPONDER, + session, + app_method_name=None, + elements=False, + ) + ) d_list.addCallback(lambda dummy: iq_elt.send()) + def changeState(dummy, session): - session['state'] = STATE_ACTIVE + session["state"] = STATE_ACTIVE d_list.addCallback(changeState, session) - d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_ACCEPTED_ACK, session, elements=False)) - d_list.addErrback(self._iqError, session['id'], client) + d_list.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_ACCEPTED_ACK, session, elements=False + ) + ) + d_list.addErrback(self._iqError, session["id"], client) return d_list def onSessionTerminate(self, client, request, jingle_elt, session): # TODO: check reason, display a message to user if needed - log.debug("Jingle Session {} terminated".format(session['id'])) + log.debug("Jingle Session {} terminated".format(session["id"])) try: - reason_elt = jingle_elt.elements(NS_JINGLE, 'reason').next() + reason_elt = jingle_elt.elements(NS_JINGLE, "reason").next() except StopIteration: log.warning(u"No reason given for session termination") - reason_elt = jingle_elt.addElement('reason') + reason_elt = jingle_elt.addElement("reason") - terminate_defers = self._callPlugins(client, XEP_0166.A_SESSION_TERMINATE, session, 'jingleTerminate', 'jingleTerminate', self._ignore, self._ignore, elements=False, force_element=reason_elt) + terminate_defers = self._callPlugins( + client, + XEP_0166.A_SESSION_TERMINATE, + session, + "jingleTerminate", + "jingleTerminate", + self._ignore, + self._ignore, + elements=False, + force_element=reason_elt, + ) terminate_dlist = defer.DeferredList(terminate_defers) - terminate_dlist.addCallback(lambda dummy: self._delSession(client, session['id'])) - client.send(xmlstream.toResponse(request, 'result')) + terminate_dlist.addCallback(lambda dummy: self._delSession(client, session["id"])) + client.send(xmlstream.toResponse(request, "result")) def onSessionAccept(self, client, request, jingle_elt, session): """Method called once session is accepted @@ -782,7 +932,7 @@ class XEP_0166(object): @param jingle_elt(domish.Element): the element @param session(dict): session data """ - log.debug(u"Jingle session {} has been accepted".format(session['id'])) + log.debug(u"Jingle session {} has been accepted".format(session["id"])) try: self._parseElements(jingle_elt, session, request, client) @@ -790,9 +940,9 @@ class XEP_0166(object): return # at this point we can send the result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) # and change the state - session['state'] = STATE_ACTIVE + session["state"] = STATE_ACTIVE negociate_defers = [] negociate_defers = self._callPlugins(client, XEP_0166.A_SESSION_ACCEPT, session) @@ -800,15 +950,21 @@ class XEP_0166(object): negociate_dlist = defer.DeferredList(negociate_defers) # after negociations we start the transfer - negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False)) + negociate_dlist.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_START, session, app_method_name=None, elements=False + ) + ) def _onSessionCb(self, result, client, request, jingle_elt, session): - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) def _onSessionEb(self, failure_, client, request, jingle_elt, session): log.error(u"Error while handling onSessionInfo: {}".format(failure_.value)) # XXX: only error managed so far, maybe some applications/transports need more - self.sendError(client, 'feature-not-implemented', None, request, 'unsupported-info') + self.sendError( + client, "feature-not-implemented", None, request, "unsupported-info" + ) def onSessionInfo(self, client, request, jingle_elt, session): """Method called when a session-info action is received from other peer @@ -821,14 +977,21 @@ class XEP_0166(object): """ if not jingle_elt.children: # this is a session ping, see XEP-0166 §6.8 - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) return try: # XXX: session-info is most likely only used for application, so we don't call transport plugins # if a future transport use it, this behaviour must be adapted - defers = self._callPlugins(client, XEP_0166.A_SESSION_INFO, session, 'jingleSessionInfo', None, - elements=False, force_element=jingle_elt) + defers = self._callPlugins( + client, + XEP_0166.A_SESSION_INFO, + session, + "jingleSessionInfo", + None, + elements=False, + force_element=jingle_elt, + ) except exceptions.NotFound as e: self._onSessionEb(failure.Failure(e), client, request, jingle_elt, session) return @@ -849,32 +1012,40 @@ class XEP_0166(object): """ log.debug(u"Other peer wants to replace the transport") try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: defer.returnValue(None) - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) content_name = None to_replace = [] - for content_name, content_data in session['contents'].iteritems(): + for content_name, content_data in session["contents"].iteritems(): try: - transport_elt = content_data.pop('transport_elt') + transport_elt = content_data.pop("transport_elt") except KeyError: continue transport_ns = transport_elt.uri try: transport = self._transports[transport_ns] except KeyError: - log.warning(u"Other peer want to replace current transport with an unknown one: {}".format(transport_ns)) + log.warning( + u"Other peer want to replace current transport with an unknown one: {}".format( + transport_ns + ) + ) content_name = None break to_replace.append((content_name, content_data, transport, transport_elt)) if content_name is None: # wa can't accept the replacement - iq_elt, reject_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REJECT) + iq_elt, reject_jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_REJECT + ) for child in jingle_elt.children: reject_jingle_elt.addChild(child) @@ -883,21 +1054,29 @@ class XEP_0166(object): # at this point, everything is alright and we can replace the transport(s) # this is similar to an session-accept action, but for transports only - iq_elt, accept_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_ACCEPT) + iq_elt, accept_jingle_elt = self._buildJingleElt( + client, session, XEP_0166.A_TRANSPORT_ACCEPT + ) for content_name, content_data, transport, transport_elt in to_replace: # we can now actually replace the transport - yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None) - content_data['transport'] = transport - content_data['transport_data'].clear() + yield content_data["transport"].handler.jingleHandler( + client, XEP_0166.A_DESTROY, session, content_name, None + ) + content_data["transport"] = transport + content_data["transport_data"].clear() # and build the element - content_elt = accept_jingle_elt.addElement('content') - content_elt['name'] = content_name - content_elt['creator'] = content_data['creator'] + content_elt = accept_jingle_elt.addElement("content") + content_elt["name"] = content_name + content_elt["creator"] = content_data["creator"] # we notify the transport and insert its in the answer - accept_transport_elt = yield transport.handler.jingleHandler(client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt) + accept_transport_elt = yield transport.handler.jingleHandler( + client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt + ) content_elt.addChild(accept_transport_elt) # there is no confirmation needed here, so we can directly prepare it - yield transport.handler.jingleHandler(client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None) + yield transport.handler.jingleHandler( + client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None + ) iq_elt.send() @@ -912,20 +1091,28 @@ class XEP_0166(object): log.debug(u"new transport has been accepted") try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: return # at this point we can send the result to confirm reception of the request - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) negociate_defers = [] - negociate_defers = self._callPlugins(client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None) + negociate_defers = self._callPlugins( + client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None + ) negociate_dlist = defer.DeferredList(negociate_defers) # after negociations we start the transfer - negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False)) + negociate_dlist.addCallback( + lambda dummy: self._callPlugins( + client, XEP_0166.A_START, session, app_method_name=None, elements=False + ) + ) def onTransportReject(self, client, request, jingle_elt, session): """Method called when a transport replacement is refused @@ -937,7 +1124,7 @@ class XEP_0166(object): """ # XXX: for now, we terminate the session in case of transport-reject # this behaviour may change in the future - self.terminate(client, 'failed-transport', session) + self.terminate(client, "failed-transport", session) def onTransportInfo(self, client, request, jingle_elt, session): """Method called when a transport-info action is received from other peer @@ -948,23 +1135,31 @@ class XEP_0166(object): @param jingle_elt(domish.Element): the element @param session(dict): session data """ - log.debug(u"Jingle session {} has been accepted".format(session['id'])) + log.debug(u"Jingle session {} has been accepted".format(session["id"])) try: - self._parseElements(jingle_elt, session, request, client, with_application=False) + self._parseElements( + jingle_elt, session, request, client, with_application=False + ) except exceptions.CancelError: return # The parsing was OK, we send the result - client.send(xmlstream.toResponse(request, 'result')) + client.send(xmlstream.toResponse(request, "result")) - for content_name, content_data in session['contents'].iteritems(): + for content_name, content_data in session["contents"].iteritems(): try: - transport_elt = content_data.pop('transport_elt') + transport_elt = content_data.pop("transport_elt") except KeyError: continue else: - content_data['transport'].handler.jingleHandler(client, XEP_0166.A_TRANSPORT_INFO, session, content_name, transport_elt) + content_data["transport"].handler.jingleHandler( + client, + XEP_0166.A_TRANSPORT_INFO, + session, + content_name, + transport_elt, + ) class XEP_0166_handler(xmlstream.XMPPHandler): @@ -974,10 +1169,12 @@ class XEP_0166_handler(xmlstream.XMPPHandler): self.plugin_parent = plugin_parent def connectionInitialized(self): - self.xmlstream.addObserver(JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent) + self.xmlstream.addObserver( + JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0184.py b/sat/plugins/plugin_xep_0184.py index 8693f3e5..bc121a25 100644 --- a/sat/plugins/plugin_xep_0184.py +++ b/sat/plugins/plugin_xep_0184.py @@ -22,27 +22,37 @@ from sat.core.log import getLogger from twisted.internet import reactor from twisted.words.protocols.jabber import xmlstream, jid from twisted.words.xish import domish + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler -NS_MESSAGE_DELIVERY_RECEIPTS = 'urn:xmpp:receipts' +NS_MESSAGE_DELIVERY_RECEIPTS = "urn:xmpp:receipts" -MSG = 'message' +MSG = "message" -MSG_CHAT = '/'+MSG+'[@type="chat"]' -MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST = MSG_CHAT+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' -MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_CHAT+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' +MSG_CHAT = "/" + MSG + '[@type="chat"]' +MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST = ( + MSG_CHAT + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) +MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = ( + MSG_CHAT + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) -MSG_NORMAL = '/'+MSG+'[@type="normal"]' -MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST = MSG_NORMAL+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' -MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_NORMAL+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]' +MSG_NORMAL = "/" + MSG + '[@type="normal"]' +MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST = ( + MSG_NORMAL + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) +MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = ( + MSG_NORMAL + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]' +) PARAM_KEY = "Privacy" @@ -51,25 +61,26 @@ ENTITY_KEY = PARAM_KEY + "_" + PARAM_NAME PLUGIN_INFO = { -C.PI_NAME: "XEP-0184 Plugin", -C.PI_IMPORT_NAME: "XEP-0184", -C.PI_TYPE: "XEP", -C.PI_PROTOCOLS: ["XEP-0184"], -C.PI_DEPENDENCIES: [], -C.PI_MAIN: "XEP_0184", -C.PI_HANDLER: "yes", -C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts""") + C.PI_NAME: "XEP-0184 Plugin", + C.PI_IMPORT_NAME: "XEP-0184", + C.PI_TYPE: "XEP", + C.PI_PROTOCOLS: ["XEP-0184"], + C.PI_DEPENDENCIES: [], + C.PI_MAIN: "XEP_0184", + C.PI_HANDLER: "yes", + C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts"""), } STATUS_MESSAGE_DELIVERY_RECEIVED = "delivered" -TEMPO_DELETE_WAITING_ACK_S = 300 # 5 min +TEMPO_DELETE_WAITING_ACK_S = 300 # 5 min class XEP_0184(object): """ Implementation for XEP 0184. """ + params = """ """ % { - 'category_name': PARAM_KEY, - 'category_label': _(PARAM_KEY), - 'param_name': PARAM_NAME, - 'param_label': _('Enable message delivery receipts') + "category_name": PARAM_KEY, + "category_label": _(PARAM_KEY), + "param_name": PARAM_NAME, + "param_label": _("Enable message delivery receipts"), } def __init__(self, host): @@ -94,24 +105,39 @@ class XEP_0184(object): host.memory.updateParams(self.params) host.trigger.add("sendMessage", self.sendMessageTrigger) - host.bridge.addSignal("messageState", ".plugin", signature='sss') # message_uid, status, profile + host.bridge.addSignal( + "messageState", ".plugin", signature="sss" + ) # message_uid, status, profile def getHandler(self, client): return XEP_0184_handler(self, client.profile) - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Install SendMessage command hook """ + def treatment(mess_data): - message = mess_data['xml'] + message = mess_data["xml"] message_type = message.getAttribute("type") - if self._isActif(client.profile) and (message_type == "chat" or message_type == "normal"): - message.addElement('request', NS_MESSAGE_DELIVERY_RECEIPTS) - uid = mess_data['uid'] + if self._isActif(client.profile) and ( + message_type == "chat" or message_type == "normal" + ): + message.addElement("request", NS_MESSAGE_DELIVERY_RECEIPTS) + uid = mess_data["uid"] msg_id = message.getAttribute("id") self._dictRequest[msg_id] = uid - reactor.callLater(TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id) - log.debug(_("[XEP-0184] Request acknowledgment for message id {}".format(msg_id))) + reactor.callLater( + TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id + ) + log.debug( + _( + "[XEP-0184] Request acknowledgment for message id {}".format( + msg_id + ) + ) + ) return mess_data @@ -122,13 +148,13 @@ class XEP_0184(object): """This method is called on message delivery receipts **request** (XEP-0184 #7) @param msg_elt: message element @param client: %(doc_client)s""" - from_jid = jid.JID(msg_elt['from']) + from_jid = jid.JID(msg_elt["from"]) if self._isActif(client.profile) and client.roster.isPresenceAuthorised(from_jid): - received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, 'received')) + received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, "received")) received_elt_ret["id"] = msg_elt["id"] - msg_result_elt = xmlstream.toResponse(msg_elt, 'result') + msg_result_elt = xmlstream.toResponse(msg_elt, "result") msg_result_elt.addChild(received_elt_ret) client.send(msg_result_elt) @@ -137,27 +163,38 @@ class XEP_0184(object): @param msg_elt: message element @param client: %(doc_client)s""" msg_elt.handled = True - rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, 'received').next() - msg_id = rcv_elt['id'] + rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, "received").next() + msg_id = rcv_elt["id"] try: uid = self._dictRequest[msg_id] del self._dictRequest[msg_id] - self.host.bridge.messageState(uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile) - log.debug(_("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id))) + self.host.bridge.messageState( + uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile + ) + log.debug( + _("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id)) + ) except KeyError: pass def _clearDictRequest(self, msg_id): try: del self._dictRequest[msg_id] - log.debug(_("[XEP-0184] Delete waiting acknowledgment for message id {}".format(msg_id))) + log.debug( + _( + "[XEP-0184] Delete waiting acknowledgment for message id {}".format( + msg_id + ) + ) + ) except KeyError: pass def _isActif(self, profile): return self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile) + class XEP_0184_handler(XMPPHandler): implements(iwokkel.IDisco) @@ -167,14 +204,30 @@ class XEP_0184_handler(XMPPHandler): self.profile = profile def connectionInitialized(self): - self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent) - self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent) + self.xmlstream.addObserver( + MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST, + self.plugin_parent.onMessageDeliveryReceiptsRequest, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, + self.plugin_parent.onMessageDeliveryReceiptsReceived, + client=self.parent, + ) - self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent) - self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent) + self.xmlstream.addObserver( + MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST, + self.plugin_parent.onMessageDeliveryReceiptsRequest, + client=self.parent, + ) + self.xmlstream.addObserver( + MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, + self.plugin_parent.onMessageDeliveryReceiptsReceived, + client=self.parent, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_MESSAGE_DELIVERY_RECEIPTS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0203.py b/sat/plugins/plugin_xep_0203.py index 9dd3309e..777878a1 100644 --- a/sat/plugins/plugin_xep_0203.py +++ b/sat/plugins/plugin_xep_0203.py @@ -21,9 +21,11 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel, delay + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -31,7 +33,7 @@ except ImportError: from zope.interface import implements -NS_DD = 'urn:xmpp:delay' +NS_DD = "urn:xmpp:delay" PLUGIN_INFO = { C.PI_NAME: "Delayed Delivery", @@ -40,12 +42,11 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0203"], C.PI_MAIN: "XEP_0203", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery""") + C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery"""), } class XEP_0203(object): - def __init__(self, host): log.info(_("Delayed Delivery plugin initialization")) self.host = host @@ -53,7 +54,7 @@ class XEP_0203(object): def getHandler(self, client): return XEP_0203_handler(self, client.profile) - def delay(self, stamp, sender=None, desc='', parent=None): + def delay(self, stamp, sender=None, desc="", parent=None): """Build a delay element, eventually append it to the given parent element. @param stamp (datetime): offset-aware timestamp of the original sending. @@ -78,8 +79,8 @@ class XEP_0203_handler(XMPPHandler): self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_DD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0231.py b/sat/plugins/plugin_xep_0231.py index 042221b3..606617ce 100644 --- a/sat/plugins/plugin_xep_0231.py +++ b/sat/plugins/plugin_xep_0231.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from wokkel import disco, iwokkel @@ -43,24 +44,29 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0231"], C.PI_MAIN: "XEP_0231", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of bits of binary (used for small images/files)""") + C.PI_DESCRIPTION: _( + """Implementation of bits of binary (used for small images/files)""" + ), } -NS_BOB = u'urn:xmpp:bob' +NS_BOB = u"urn:xmpp:bob" IQ_BOB_REQUEST = C.IQ_GET + '/data[@xmlns="' + NS_BOB + '"]' class XEP_0231(object): - def __init__(self, host): log.info(_(u"plugin Bits of Binary initialization")) self.host = host - host.registerNamespace('bob', NS_BOB) + host.registerNamespace("bob", NS_BOB) host.trigger.add("xhtml_post_treat", self.XHTMLTrigger) - host.bridge.addMethod("bobGetFile", ".plugin", - in_sign='sss', out_sign='s', - method=self._getFile, - async=True) + host.bridge.addMethod( + "bobGetFile", + ".plugin", + in_sign="sss", + out_sign="s", + method=self._getFile, + async=True, + ) def dumpData(self, cache, data_elt, cid): """save file encoded in data_elt to cache @@ -70,21 +76,19 @@ class XEP_0231(object): @param cid(unicode): content-id @return(unicode): full path to dumped file """ - # FIXME: is it needed to use a separate thread? + # FIXME: is it needed to use a separate thread? # probably not with the little data expected with BoB try: - max_age = int(data_elt['max-age']) + max_age = int(data_elt["max-age"]) if max_age < 0: raise ValueError except (KeyError, ValueError): - log.warning(u'invalid max-age found') + log.warning(u"invalid max-age found") max_age = None with cache.cacheData( - PLUGIN_INFO[C.PI_IMPORT_NAME], - cid, - data_elt.getAttribute('type'), - max_age) as f: + PLUGIN_INFO[C.PI_IMPORT_NAME], cid, data_elt.getAttribute("type"), max_age + ) as f: file_path = f.name f.write(base64.b64decode(str(data_elt))) @@ -95,15 +99,16 @@ class XEP_0231(object): return XEP_0231_handler(self) def _requestCb(self, iq_elt, cache, cid): - for data_elt in iq_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in iq_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: file_path = self.dumpData(cache, data_elt, cid) return file_path - log.warning(u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format( - iq_elt = iq_elt, - cid = cid - )) + log.warning( + u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format( + iq_elt=iq_elt, cid=cid + ) + ) raise failure.Failure(exceptions.DataError("missing data")) def _requestEb(self, failure_): @@ -122,39 +127,39 @@ class XEP_0231(object): """ if cache is None: cache = client.cache - iq_elt = client.IQ('get') - iq_elt['to'] = to_jid.full() - data_elt = iq_elt.addElement((NS_BOB, 'data')) - data_elt['cid'] = cid + iq_elt = client.IQ("get") + iq_elt["to"] = to_jid.full() + data_elt = iq_elt.addElement((NS_BOB, "data")) + data_elt["cid"] = cid d = iq_elt.send() d.addCallback(self._requestCb, cache, cid) d.addErrback(self._requestEb) return d def _setImgEltSrc(self, path, img_elt): - img_elt[u'src'] = u'file://{}'.format(path) + img_elt[u"src"] = u"file://{}".format(path) def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d): - for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u'img'): - source = img_elt.getAttribute(u'src','') - if source.startswith(u'cid:'): + for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u"img"): + source = img_elt.getAttribute(u"src", "") + if source.startswith(u"cid:"): cid = source[4:] file_path = client.cache.getFilePath(cid) if file_path is not None: - # image is in cache, we change the url - img_elt[u'src'] = u'file://{}'.format(file_path) + # image is in cache, we change the url + img_elt[u"src"] = u"file://{}".format(file_path) continue else: # image is not in cache, is it given locally? - for data_elt in message_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in message_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: file_path = self.dumpData(client.cache, data_elt, cid) - img_elt[u'src'] = u'file://{}'.format(file_path) + img_elt[u"src"] = u"file://{}".format(file_path) break else: # cid not found locally, we need to request it # so we use the deferred - d = self.requestData(client, jid.JID(message_elt['from']), cid) + d = self.requestData(client, jid.JID(message_elt["from"]), cid) d.addCallback(partial(self._setImgEltSrc, img_elt=img_elt)) treat_d.addCallback(lambda dummy: d) @@ -165,28 +170,28 @@ class XEP_0231(object): # An access check should be implemented though. iq_elt.handled = True - data_elt = next(iq_elt.elements(NS_BOB, 'data')) + data_elt = next(iq_elt.elements(NS_BOB, "data")) try: - cid = data_elt[u'cid'] + cid = data_elt[u"cid"] except KeyError: - error_elt = jabber_error.StanzaError('not-acceptable').toResponse(iq_elt) + error_elt = jabber_error.StanzaError("not-acceptable").toResponse(iq_elt) client.send(error_elt) return metadata = self.host.common_cache.getMetadata(cid) if metadata is None: - error_elt = jabber_error.StanzaError('item-not-found').toResponse(iq_elt) + error_elt = jabber_error.StanzaError("item-not-found").toResponse(iq_elt) client.send(error_elt) return - with open(metadata['path']) as f: + with open(metadata["path"]) as f: data = f.read() - result_elt = xmlstream.toResponse(iq_elt, 'result') - data_elt = result_elt.addElement((NS_BOB, 'data'), content = data.encode('base64')) - data_elt[u'cid'] = cid - data_elt[u'type'] = metadata[u'mime_type'] - data_elt[u'max-age'] = unicode(int(max(0, metadata['eol'] - time.time()))) + result_elt = xmlstream.toResponse(iq_elt, "result") + data_elt = result_elt.addElement((NS_BOB, "data"), content=data.encode("base64")) + data_elt[u"cid"] = cid + data_elt[u"type"] = metadata[u"mime_type"] + data_elt[u"max-age"] = unicode(int(max(0, metadata["eol"] - time.time()))) client.send(result_elt) def _getFile(self, peer_jid_s, cid, profile): @@ -207,13 +212,13 @@ class XEP_0231(object): """ file_path = client.cache.getFilePath(cid) if file_path is not None: - # file is in cache + # file is in cache return defer.succeed(file_path) else: # file not in cache, is it given locally? if parent_elt is not None: - for data_elt in parent_elt.elements(NS_BOB, u'data'): - if data_elt.getAttribute('cid') == cid: + for data_elt in parent_elt.elements(NS_BOB, u"data"): + if data_elt.getAttribute("cid") == cid: return defer.succeed(self.dumpData(client.cache, data_elt, cid)) # cid not found locally, we need to request it @@ -230,10 +235,12 @@ class XEP_0231_handler(xmlstream.XMPPHandler): def connectionInitialized(self): if self.parent.is_component: - self.xmlstream.addObserver(IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_BOB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0234.py b/sat/plugins/plugin_xep_0234.py index 26a11cfd..bd3041bf 100644 --- a/sat/plugins/plugin_xep_0234.py +++ b/sat/plugins/plugin_xep_0234.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -40,7 +41,7 @@ from sat.tools.common import regex import mimetypes -NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:5' +NS_JINGLE_FT = "urn:xmpp:jingle:apps:file-transfer:5" PLUGIN_INFO = { C.PI_NAME: "Jingle File Transfer", @@ -51,11 +52,11 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0300", "FILE"], C.PI_MAIN: "XEP_0234", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer""") + C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""), } -EXTRA_ALLOWED = {u'path', u'namespace', u'file_desc', u'file_hash'} -Range = namedtuple('Range', ('offset', 'length')) +EXTRA_ALLOWED = {u"path", u"namespace", u"file_desc", u"file_hash"} +Range = namedtuple("Range", ("offset", "length")) class XEP_0234(object): @@ -66,14 +67,30 @@ class XEP_0234(object): def __init__(self, host): log.info(_("plugin Jingle File Transfer initialization")) self.host = host - host.registerNamespace('jingle-ft', NS_JINGLE_FT) - self._j = host.plugins["XEP-0166"] # shortcut to access jingle + host.registerNamespace("jingle-ft", NS_JINGLE_FT) + self._j = host.plugins["XEP-0166"] # shortcut to access jingle self._j.registerApplication(NS_JINGLE_FT, self) self._f = host.plugins["FILE"] - self._f.register(NS_JINGLE_FT, self.fileJingleSend, priority = 10000, method_name=u"Jingle") + self._f.register( + NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name=u"Jingle" + ) self._hash = self.host.plugins["XEP-0300"] - host.bridge.addMethod("fileJingleSend", ".plugin", in_sign='ssssa{ss}s', out_sign='', method=self._fileJingleSend, async=True) - host.bridge.addMethod("fileJingleRequest", ".plugin", in_sign='sssssa{ss}s', out_sign='s', method=self._fileJingleRequest, async=True) + host.bridge.addMethod( + "fileJingleSend", + ".plugin", + in_sign="ssssa{ss}s", + out_sign="", + method=self._fileJingleSend, + async=True, + ) + host.bridge.addMethod( + "fileJingleRequest", + ".plugin", + in_sign="sssssa{ss}s", + out_sign="s", + method=self._fileJingleRequest, + async=True, + ) def getHandler(self, client): return XEP_0234_handler() @@ -85,12 +102,25 @@ class XEP_0234(object): @param content_name(unicode): name of the content @return (unicode): unique progress id """ - return u'{}_{}'.format(session['id'], content_name) + return u"{}_{}".format(session["id"], content_name) # generic methods - def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None, mime_type=None, desc=None, - modified=None, transfer_range=None, path=None, namespace=None, file_elt=None, **kwargs): + def buildFileElement( + self, + name, + file_hash=None, + hash_algo=None, + size=None, + mime_type=None, + desc=None, + modified=None, + transfer_range=None, + path=None, + namespace=None, + file_elt=None, + **kwargs + ): """Generate a @@ -79,10 +90,10 @@ class XEP_0184(object): element with available metadata @param file_hash(unicode, None): hash of the file @@ -109,26 +139,32 @@ class XEP_0234(object): @trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add """ if file_elt is None: - file_elt = domish.Element((NS_JINGLE_FT, u'file')) - for name, value in ((u'name', name), (u'size', size), ('media-type', mime_type), - (u'desc', desc), (u'path', path), (u'namespace', namespace)): + file_elt = domish.Element((NS_JINGLE_FT, u"file")) + for name, value in ( + (u"name", name), + (u"size", size), + ("media-type", mime_type), + (u"desc", desc), + (u"path", path), + (u"namespace", namespace), + ): if value is not None: file_elt.addElement(name, content=unicode(value)) if modified is not None: if isinstance(modified, int): - file_elt.addElement(u'date', utils.xmpp_date(modified or None)) + file_elt.addElement(u"date", utils.xmpp_date(modified or None)) else: - file_elt.addElement(u'date', modified) - elif 'created' in kwargs: - file_elt.addElement(u'date', utils.xmpp_date(kwargs.pop('created'))) + file_elt.addElement(u"date", modified) + elif "created" in kwargs: + file_elt.addElement(u"date", utils.xmpp_date(kwargs.pop("created"))) - range_elt = file_elt.addElement(u'range') + range_elt = file_elt.addElement(u"range") if transfer_range is not None: if transfer_range.offset is not None: - range_elt[u'offset'] = transfer_range.offset + range_elt[u"offset"] = transfer_range.offset if transfer_range.length is not None: - range_elt[u'length'] = transfer_range.length + range_elt[u"length"] = transfer_range.length if file_hash is not None: if not file_hash: file_elt.addChild(self._hash.buildHashUsedElt()) @@ -136,10 +172,10 @@ class XEP_0234(object): file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo)) elif hash_algo is not None: file_elt.addChild(self._hash.buildHashUsedElt(hash_algo)) - self.host.trigger.point(u'XEP-0234_buildFileElement', file_elt, extra_args=kwargs) + self.host.trigger.point(u"XEP-0234_buildFileElement", file_elt, extra_args=kwargs) if kwargs: for kw in kwargs: - log.debug('ignored keyword: {}'.format(kw)) + log.debug("ignored keyword: {}".format(kw)) return file_elt def buildFileElementFromDict(self, file_data, **kwargs): @@ -151,10 +187,16 @@ class XEP_0234(object): if kwargs: file_data = file_data.copy() file_data.update(kwargs) - return self. buildFileElement(**file_data) + return self.buildFileElement(**file_data) - - def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None, keep_empty_range=False): + def parseFileElement( + self, + file_elt, + file_data=None, + given=False, + parent_elt=None, + keep_empty_range=False, + ): """Parse a element and file dictionary accordingly @param file_data(dict, None): dict where the data will be set @@ -173,77 +215,105 @@ class XEP_0234(object): """ if parent_elt is not None: if file_elt is not None: - raise exceptions.InternalError(u'file_elt must be None if parent_elt is set') + raise exceptions.InternalError( + u"file_elt must be None if parent_elt is set" + ) try: - file_elt = next(parent_elt.elements(NS_JINGLE_FT, u'file')) + file_elt = next(parent_elt.elements(NS_JINGLE_FT, u"file")) except StopIteration: raise exceptions.NotFound() else: if not file_elt or file_elt.uri != NS_JINGLE_FT: - raise exceptions.DataError(u'invalid element: {stanza}'.format(stanza = file_elt.toXml())) + raise exceptions.DataError( + u"invalid element: {stanza}".format(stanza=file_elt.toXml()) + ) if file_data is None: file_data = {} - for name in (u'name', u'desc', u'path', u'namespace'): + for name in (u"name", u"desc", u"path", u"namespace"): try: file_data[name] = unicode(next(file_elt.elements(NS_JINGLE_FT, name))) except StopIteration: pass - - name = file_data.get(u'name') - if name == u'..': + name = file_data.get(u"name") + if name == u"..": # we don't want to go to parent dir when joining to a path - name = u'--' - file_data[u'name'] = name - elif name is not None and u'/' in name or u'\\' in name: - file_data[u'name'] = regex.pathEscape(name) + name = u"--" + file_data[u"name"] = name + elif name is not None and u"/" in name or u"\\" in name: + file_data[u"name"] = regex.pathEscape(name) try: - file_data[u'mime_type'] = unicode(next(file_elt.elements(NS_JINGLE_FT, u'media-type'))) + file_data[u"mime_type"] = unicode( + next(file_elt.elements(NS_JINGLE_FT, u"media-type")) + ) except StopIteration: pass try: - file_data[u'size'] = int(unicode(next(file_elt.elements(NS_JINGLE_FT, u'size')))) + file_data[u"size"] = int( + unicode(next(file_elt.elements(NS_JINGLE_FT, u"size"))) + ) except StopIteration: pass try: - file_data[u'modified'] = date_utils.date_parse(next(file_elt.elements(NS_JINGLE_FT, u'date'))) + file_data[u"modified"] = date_utils.date_parse( + next(file_elt.elements(NS_JINGLE_FT, u"date")) + ) except StopIteration: pass try: - range_elt = file_elt.elements(NS_JINGLE_FT, u'range').next() + range_elt = file_elt.elements(NS_JINGLE_FT, u"range").next() except StopIteration: pass else: - offset = range_elt.getAttribute('offset') - length = range_elt.getAttribute('length') + offset = range_elt.getAttribute("offset") + length = range_elt.getAttribute("length") if offset or length or keep_empty_range: - file_data[u'transfer_range'] = Range(offset=offset, length=length) + file_data[u"transfer_range"] = Range(offset=offset, length=length) - prefix = u'given_' if given else u'' - hash_algo_key, hash_key = u'hash_algo', prefix + u'file_hash' + prefix = u"given_" if given else u"" + hash_algo_key, hash_key = u"hash_algo", prefix + u"file_hash" try: - file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt(file_elt) + file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt( + file_elt + ) except exceptions.NotFound: pass - self.host.trigger.point(u'XEP-0234_parseFileElement', file_elt, file_data) + self.host.trigger.point(u"XEP-0234_parseFileElement", file_elt, file_data) return file_data # bridge methods - def _fileJingleSend(self, peer_jid, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE): + def _fileJingleSend( + self, + peer_jid, + filepath, + name="", + file_desc="", + extra=None, + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) - return self.fileJingleSend(client, jid.JID(peer_jid), filepath, name or None, file_desc or None, extra or None) + return self.fileJingleSend( + client, + jid.JID(peer_jid), + filepath, + name or None, + file_desc or None, + extra or None, + ) @defer.inlineCallbacks - def fileJingleSend(self, client, peer_jid, filepath, name, file_desc=None, extra=None): + def fileJingleSend( + self, client, peer_jid, filepath, name, file_desc=None, extra=None + ): """Send a file using jingle file transfer @param peer_jid(jid.JID): destinee jid @@ -256,25 +326,58 @@ class XEP_0234(object): if extra is None: extra = {} if file_desc is not None: - extra['file_desc'] = file_desc - yield self._j.initiate(client, - peer_jid, - [{'app_ns': NS_JINGLE_FT, - 'senders': self._j.ROLE_INITIATOR, - 'app_kwargs': {'filepath': filepath, - 'name': name, - 'extra': extra, - 'progress_id_d': progress_id_d}, - }]) + extra["file_desc"] = file_desc + yield self._j.initiate( + client, + peer_jid, + [ + { + "app_ns": NS_JINGLE_FT, + "senders": self._j.ROLE_INITIATOR, + "app_kwargs": { + "filepath": filepath, + "name": name, + "extra": extra, + "progress_id_d": progress_id_d, + }, + } + ], + ) progress_id = yield progress_id_d defer.returnValue(progress_id) - def _fileJingleRequest(self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None, profile=C.PROF_KEY_NONE): + def _fileJingleRequest( + self, + peer_jid, + filepath, + name="", + file_hash="", + hash_algo="", + extra=None, + profile=C.PROF_KEY_NONE, + ): client = self.host.getClient(profile) - return self.fileJingleRequest(client, jid.JID(peer_jid), filepath, name or None, file_hash or None, hash_algo or None, extra or None) + return self.fileJingleRequest( + client, + jid.JID(peer_jid), + filepath, + name or None, + file_hash or None, + hash_algo or None, + extra or None, + ) @defer.inlineCallbacks - def fileJingleRequest(self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None, extra=None): + def fileJingleRequest( + self, + client, + peer_jid, + filepath, + name=None, + file_hash=None, + hash_algo=None, + extra=None, + ): """Request a file using jingle file transfer @param peer_jid(jid.JID): destinee jid @@ -289,122 +392,152 @@ class XEP_0234(object): if file_hash is not None: if hash_algo is None: raise ValueError(_(u"hash_algo must be set if file_hash is set")) - extra['file_hash'] = file_hash - extra['hash_algo'] = hash_algo + extra["file_hash"] = file_hash + extra["hash_algo"] = hash_algo else: if hash_algo is not None: raise ValueError(_(u"file_hash must be set if hash_algo is set")) - yield self._j.initiate(client, - peer_jid, - [{'app_ns': NS_JINGLE_FT, - 'senders': self._j.ROLE_RESPONDER, - 'app_kwargs': {'filepath': filepath, - 'name': name, - 'extra': extra, - 'progress_id_d': progress_id_d}, - }]) + yield self._j.initiate( + client, + peer_jid, + [ + { + "app_ns": NS_JINGLE_FT, + "senders": self._j.ROLE_RESPONDER, + "app_kwargs": { + "filepath": filepath, + "name": name, + "extra": extra, + "progress_id_d": progress_id_d, + }, + } + ], + ) progress_id = yield progress_id_d defer.returnValue(progress_id) # jingle callbacks - def jingleSessionInit(self, client, session, content_name, filepath, name, extra, progress_id_d): + def jingleSessionInit( + self, client, session, content_name, filepath, name, extra, progress_id_d + ): if extra is None: extra = {} else: if not EXTRA_ALLOWED.issuperset(extra): - raise ValueError(_(u"only the following keys are allowed in extra: {keys}").format( - keys=u', '.join(EXTRA_ALLOWED))) + raise ValueError( + _(u"only the following keys are allowed in extra: {keys}").format( + keys=u", ".join(EXTRA_ALLOWED) + ) + ) progress_id_d.callback(self.getProgressId(session, content_name)) - content_data = session['contents'][content_name] - application_data = content_data['application_data'] - assert 'file_path' not in application_data - application_data['file_path'] = filepath - file_data = application_data['file_data'] = {} - desc_elt = domish.Element((NS_JINGLE_FT, 'description')) + content_data = session["contents"][content_name] + application_data = content_data["application_data"] + assert "file_path" not in application_data + application_data["file_path"] = filepath + file_data = application_data["file_data"] = {} + desc_elt = domish.Element((NS_JINGLE_FT, "description")) file_elt = desc_elt.addElement("file") - if content_data[u'senders'] == self._j.ROLE_INITIATOR: + if content_data[u"senders"] == self._j.ROLE_INITIATOR: # we send a file if name is None: name = os.path.basename(filepath) - file_data[u'date'] = utils.xmpp_date() - file_data[u'desc'] = extra.pop(u'file_desc', u'') - file_data[u'name'] = name + file_data[u"date"] = utils.xmpp_date() + file_data[u"desc"] = extra.pop(u"file_desc", u"") + file_data[u"name"] = name mime_type = mimetypes.guess_type(name, strict=False)[0] if mime_type is not None: - file_data[u'mime_type'] = mime_type - file_data[u'size'] = os.path.getsize(filepath) - if u'namespace' in extra: - file_data[u'namespace'] = extra[u'namespace'] - if u'path' in extra: - file_data[u'path'] = extra[u'path'] - self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u'') + file_data[u"mime_type"] = mime_type + file_data[u"size"] = os.path.getsize(filepath) + if u"namespace" in extra: + file_data[u"namespace"] = extra[u"namespace"] + if u"path" in extra: + file_data[u"path"] = extra[u"path"] + self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u"") else: # we request a file - file_hash = extra.pop(u'file_hash', u'') + file_hash = extra.pop(u"file_hash", u"") if not name and not file_hash: - raise ValueError(_(u'you need to provide at least name or file hash')) + raise ValueError(_(u"you need to provide at least name or file hash")) if name: - file_data[u'name'] = name + file_data[u"name"] = name if file_hash: - file_data[u'file_hash'] = file_hash - file_data[u'hash_algo'] = extra[u'hash_algo'] + file_data[u"file_hash"] = file_hash + file_data[u"hash_algo"] = extra[u"hash_algo"] else: - file_data[u'hash_algo'] = self._hash.getDefaultAlgo() - if u'namespace' in extra: - file_data[u'namespace'] = extra[u'namespace'] - if u'path' in extra: - file_data[u'path'] = extra[u'path'] + file_data[u"hash_algo"] = self._hash.getDefaultAlgo() + if u"namespace" in extra: + file_data[u"namespace"] = extra[u"namespace"] + if u"path" in extra: + file_data[u"path"] = extra[u"path"] self.buildFileElementFromDict(file_data, file_elt=file_elt) return desc_elt def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt): """This method request confirmation for a jingle session""" - content_data = session['contents'][content_name] - senders = content_data[u'senders'] + content_data = session["contents"][content_name] + senders = content_data[u"senders"] if senders not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER): log.warning(u"Bad sender, assuming initiator") - senders = content_data[u'senders'] = self._j.ROLE_INITIATOR + senders = content_data[u"senders"] = self._j.ROLE_INITIATOR # first we grab file informations try: - file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next() + file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() except StopIteration: raise failure.Failure(exceptions.DataError) - file_data = {'progress_id': self.getProgressId(session, content_name)} + file_data = {"progress_id": self.getProgressId(session, content_name)} if senders == self._j.ROLE_RESPONDER: # we send the file - return self._fileSendingRequestConf(client, session, content_data, content_name, file_data, file_elt) + return self._fileSendingRequestConf( + client, session, content_data, content_name, file_data, file_elt + ) else: # we receive the file - return self._fileReceivingRequestConf(client, session, content_data, content_name, file_data, file_elt) + return self._fileReceivingRequestConf( + client, session, content_data, content_name, file_data, file_elt + ) @defer.inlineCallbacks - def _fileSendingRequestConf(self, client, session, content_data, content_name, file_data, file_elt): + def _fileSendingRequestConf( + self, client, session, content_data, content_name, file_data, file_elt + ): """parse file_elt, and handle file retrieving/permission checking""" self.parseFileElement(file_elt, file_data) - content_data['application_data']['file_data'] = file_data - finished_d = content_data['finished_d'] = defer.Deferred() + content_data["application_data"]["file_data"] = file_data + finished_d = content_data["finished_d"] = defer.Deferred() # confirmed_d is a deferred returning confimed value (only used if cont is False) - cont, confirmed_d = self.host.trigger.returnPoint("XEP-0234_fileSendingRequest", client, session, content_data, content_name, file_data, file_elt) + cont, confirmed_d = self.host.trigger.returnPoint( + "XEP-0234_fileSendingRequest", + client, + session, + content_data, + content_name, + file_data, + file_elt, + ) if not cont: confirmed = yield confirmed_d if confirmed: args = [client, session, content_name, content_data] - finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) + finished_d.addCallbacks( + self._finishedCb, self._finishedEb, args, None, args + ) defer.returnValue(confirmed) - log.warning(_(u'File continue is not implemented yet')) + log.warning(_(u"File continue is not implemented yet")) defer.returnValue(False) - def _fileReceivingRequestConf(self, client, session, content_data, content_name, file_data, file_elt): + def _fileReceivingRequestConf( + self, client, session, content_data, content_name, file_data, file_elt + ): """parse file_elt, and handle user permission/file opening""" self.parseFileElement(file_elt, file_data, given=True) try: - hash_algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt) + hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) except exceptions.NotFound: try: hash_algo = self._hash.parseHashUsedElt(file_elt) @@ -412,88 +545,94 @@ class XEP_0234(object): raise failure.Failure(exceptions.DataError) if hash_algo is not None: - file_data['hash_algo'] = hash_algo - file_data['hash_hasher'] = hasher = self._hash.getHasher(hash_algo) - file_data['data_cb'] = lambda data: hasher.update(data) + file_data["hash_algo"] = hash_algo + file_data["hash_hasher"] = hasher = self._hash.getHasher(hash_algo) + file_data["data_cb"] = lambda data: hasher.update(data) try: - file_data['size'] = int(file_data['size']) + file_data["size"] = int(file_data["size"]) except ValueError: raise failure.Failure(exceptions.DataError) - name = file_data['name'] - if '/' in name or '\\' in name: - log.warning(u"File name contain path characters, we replace them: {}".format(name)) - file_data['name'] = name.replace('/', '_').replace('\\', '_') + name = file_data["name"] + if "/" in name or "\\" in name: + log.warning( + u"File name contain path characters, we replace them: {}".format(name) + ) + file_data["name"] = name.replace("/", "_").replace("\\", "_") - content_data['application_data']['file_data'] = file_data + content_data["application_data"]["file_data"] = file_data # now we actualy request permission to user def gotConfirmation(confirmed): if confirmed: args = [client, session, content_name, content_data] - finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) + finished_d.addCallbacks( + self._finishedCb, self._finishedEb, args, None, args + ) return confirmed # deferred to track end of transfer - finished_d = content_data['finished_d'] = defer.Deferred() - d = self._f.getDestDir(client, session['peer_jid'], content_data, file_data, stream_object=True) + finished_d = content_data["finished_d"] = defer.Deferred() + d = self._f.getDestDir( + client, session["peer_jid"], content_data, file_data, stream_object=True + ) d.addCallback(gotConfirmation) return d def jingleHandler(self, client, action, session, content_name, desc_elt): - content_data = session['contents'][content_name] - application_data = content_data['application_data'] + content_data = session["contents"][content_name] + application_data = content_data["application_data"] if action in (self._j.A_ACCEPTED_ACK,): pass elif action == self._j.A_SESSION_INITIATE: - file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next() + file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next() try: - file_elt.elements(NS_JINGLE_FT, 'range').next() + file_elt.elements(NS_JINGLE_FT, "range").next() except StopIteration: # initiator doesn't manage , but we do so we advertise it - # FIXME: to be checked + # FIXME: to be checked log.debug("adding element") - file_elt.addElement('range') + file_elt.addElement("range") elif action == self._j.A_SESSION_ACCEPT: - assert not 'stream_object' in content_data - file_data = application_data['file_data'] - file_path = application_data['file_path'] - senders = content_data[u'senders'] - if senders != session[u'role']: + assert not "stream_object" in content_data + file_data = application_data["file_data"] + file_path = application_data["file_path"] + senders = content_data[u"senders"] + if senders != session[u"role"]: # we are receiving the file try: # did the responder specified the size of the file? - file_elt = next(desc_elt.elements(NS_JINGLE_FT, u'file')) - size_elt = next(file_elt.elements(NS_JINGLE_FT, u'size')) + file_elt = next(desc_elt.elements(NS_JINGLE_FT, u"file")) + size_elt = next(file_elt.elements(NS_JINGLE_FT, u"size")) size = int(unicode(size_elt)) except (StopIteration, ValueError): size = None # XXX: hash security is not critical here, so we just take the higher mandatory one - hasher = file_data['hash_hasher'] = self._hash.getHasher() - content_data['stream_object'] = stream.FileStreamObject( + hasher = file_data["hash_hasher"] = self._hash.getHasher() + content_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, - mode='wb', + mode="wb", uid=self.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) + ) else: # we are sending the file - size = file_data['size'] + size = file_data["size"] # XXX: hash security is not critical here, so we just take the higher mandatory one - hasher = file_data['hash_hasher'] = self._hash.getHasher() - content_data['stream_object'] = stream.FileStreamObject( + hasher = file_data["hash_hasher"] = self._hash.getHasher() + content_data["stream_object"] = stream.FileStreamObject( self.host, client, file_path, uid=self.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) - finished_d = content_data['finished_d'] = defer.Deferred() + ) + finished_d = content_data["finished_d"] = defer.Deferred() args = [client, session, content_name, content_data] finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args) else: @@ -506,33 +645,42 @@ class XEP_0234(object): manage checksum, and ignore element """ # TODO: manage element - content_data = session['contents'][content_name] + content_data = session["contents"][content_name] elts = [elt for elt in jingle_elt.elements() if elt.uri == NS_JINGLE_FT] if not elts: return for elt in elts: - if elt.name == 'received': + if elt.name == "received": pass - elif elt.name == 'checksum': + elif elt.name == "checksum": # we have received the file hash, we need to parse it - if content_data['senders'] == session['role']: - log.warning(u"unexpected checksum received while we are the file sender") + if content_data["senders"] == session["role"]: + log.warning( + u"unexpected checksum received while we are the file sender" + ) raise exceptions.DataError - info_content_name = elt['name'] + info_content_name = elt["name"] if info_content_name != content_name: # it was for an other content... return - file_data = content_data['application_data']['file_data'] + file_data = content_data["application_data"]["file_data"] try: - file_elt = elt.elements((NS_JINGLE_FT, 'file')).next() + file_elt = elt.elements((NS_JINGLE_FT, "file")).next() except StopIteration: raise exceptions.DataError - algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt) - if algo != file_data.get('hash_algo'): - log.warning(u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]" - .format(peer_algo=algo, our_algo=file_data.get('hash_algo'), profile=client.profile)) + algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt) + if algo != file_data.get("hash_algo"): + log.warning( + u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format( + peer_algo=algo, + our_algo=file_data.get("hash_algo"), + profile=client.profile, + ) + ) else: - self._receiverTryTerminate(client, session, content_name, content_data) + self._receiverTryTerminate( + client, session, content_name, content_data + ) else: raise NotImplementedError @@ -540,23 +688,27 @@ class XEP_0234(object): if jingle_elt.decline: # progress is the only way to tell to frontends that session has been declined progress_id = self.getProgressId(session, content_name) - self.host.bridge.progressError(progress_id, C.PROGRESS_ERROR_DECLINED, client.profile) + self.host.bridge.progressError( + progress_id, C.PROGRESS_ERROR_DECLINED, client.profile + ) def _sendCheckSum(self, client, session, content_name, content_data): """Send the session-info with the hash checksum""" - file_data = content_data['application_data']['file_data'] - hasher = file_data['hash_hasher'] + file_data = content_data["application_data"]["file_data"] + hasher = file_data["hash_hasher"] hash_ = hasher.hexdigest() log.debug(u"Calculated hash: {}".format(hash_)) iq_elt, jingle_elt = self._j.buildSessionInfo(client, session) - checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, 'checksum')) - checksum_elt['creator'] = content_data['creator'] - checksum_elt['name'] = content_name - file_elt = checksum_elt.addElement('file') + checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum")) + checksum_elt["creator"] = content_data["creator"] + checksum_elt["name"] = content_name + file_elt = checksum_elt.addElement("file") file_elt.addChild(self._hash.buildHashElt(hash_)) iq_elt.send() - def _receiverTryTerminate(self, client, session, content_name, content_data, last_try=False): + def _receiverTryTerminate( + self, client, session, content_name, content_data, last_try=False + ): """Try to terminate the session This method must only be used by the receiver. @@ -565,70 +717,85 @@ class XEP_0234(object): @param last_try(bool): if True this mean than session must be terminated even given hash is not available @return (bool): True if session was terminated """ - if not content_data.get('transfer_finished', False): + if not content_data.get("transfer_finished", False): return False - file_data = content_data['application_data']['file_data'] - given_hash = file_data.get('given_file_hash') + file_data = content_data["application_data"]["file_data"] + given_hash = file_data.get("given_file_hash") if given_hash is None: if last_try: - log.warning(u"sender didn't sent hash checksum, we can't check the file [{profile}]".format(profile=client.profile)) + log.warning( + u"sender didn't sent hash checksum, we can't check the file [{profile}]".format( + profile=client.profile + ) + ) self._j.delayedContentTerminate(client, session, content_name) - content_data['stream_object'].close() + content_data["stream_object"].close() return True return False - hasher = file_data['hash_hasher'] + hasher = file_data["hash_hasher"] hash_ = hasher.hexdigest() if hash_ == given_hash: log.info(u"Hash checked, file was successfully transfered: {}".format(hash_)) - progress_metadata = {'hash': hash_, - 'hash_algo': file_data['hash_algo'], - 'hash_verified': C.BOOL_TRUE - } + progress_metadata = { + "hash": hash_, + "hash_algo": file_data["hash_algo"], + "hash_verified": C.BOOL_TRUE, + } error = None else: log.warning(u"Hash mismatch, the file was not transfered correctly") - progress_metadata=None + progress_metadata = None error = u"Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format( - algo = file_data['hash_algo'], - given = given_hash, - our = hash_) + algo=file_data["hash_algo"], given=given_hash, our=hash_ + ) self._j.delayedContentTerminate(client, session, content_name) - content_data['stream_object'].close(progress_metadata, error) + content_data["stream_object"].close(progress_metadata, error) # we may have the last_try timer still active, so we try to cancel it try: - content_data['last_try_timer'].cancel() + content_data["last_try_timer"].cancel() except (KeyError, internet_error.AlreadyCalled): pass return True def _finishedCb(self, dummy, client, session, content_name, content_data): log.info(u"File transfer terminated") - if content_data['senders'] != session['role']: + if content_data["senders"] != session["role"]: # we terminate the session only if we are the receiver, # as recommanded in XEP-0234 §2 (after example 6) - content_data['transfer_finished'] = True - if not self._receiverTryTerminate(client, session, content_name, content_data): + content_data["transfer_finished"] = True + if not self._receiverTryTerminate( + client, session, content_name, content_data + ): # we have not received the hash yet, we wait 5 more seconds - content_data['last_try_timer'] = reactor.callLater( - 5, self._receiverTryTerminate, client, session, content_name, content_data, last_try=True) + content_data["last_try_timer"] = reactor.callLater( + 5, + self._receiverTryTerminate, + client, + session, + content_name, + content_data, + last_try=True, + ) else: # we are the sender, we send the checksum self._sendCheckSum(client, session, content_name, content_data) - content_data['stream_object'].close() + content_data["stream_object"].close() def _finishedEb(self, failure, client, session, content_name, content_data): log.warning(u"Error while streaming file: {}".format(failure)) - content_data['stream_object'].close() - self._j.contentTerminate(client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT) + content_data["stream_object"].close() + self._j.contentTerminate( + client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT + ) class XEP_0234_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_FT)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0249.py b/sat/plugins/plugin_xep_0249.py index b416412d..250daec1 100755 --- a/sat/plugins/plugin_xep_0249.py +++ b/sat/plugins/plugin_xep_0249.py @@ -21,6 +21,7 @@ from sat.core.i18n import _, D_ from sat.core.constants import Const as C from sat.core import exceptions from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import xml_tools from twisted.words.xish import domish @@ -36,8 +37,8 @@ try: except ImportError: from wokkel.subprotocols import XMPPHandler -MESSAGE = '/message' -NS_DIRECT_MUC_INVITATION = 'jabber:x:conference' +MESSAGE = "/message" +NS_DIRECT_MUC_INVITATION = "jabber:x:conference" DIRECT_MUC_INVITATION_REQUEST = MESSAGE + '/x[@xmlns="' + NS_DIRECT_MUC_INVITATION + '"]' AUTOJOIN_KEY = "Misc" AUTOJOIN_NAME = "Auto-join MUC on invitation" @@ -52,7 +53,7 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], C.PI_MAIN: "XEP_0249", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations""") + C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations"""), } @@ -69,20 +70,26 @@ class XEP_0249(object): """ % { - 'category_name': AUTOJOIN_KEY, - 'category_label': _("Misc"), - 'param_name': AUTOJOIN_NAME, - 'param_label': _("Auto-join MUC on invitation"), - 'param_options': '\n'.join(['' % \ - (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else '') \ - for value in AUTOJOIN_VALUES]) + "category_name": AUTOJOIN_KEY, + "category_label": _("Misc"), + "param_name": AUTOJOIN_NAME, + "param_label": _("Auto-join MUC on invitation"), + "param_options": "\n".join( + [ + '' + % (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else "") + for value in AUTOJOIN_VALUES + ] + ), } def __init__(self, host): log.info(_("Plugin XEP_0249 initialization")) self.host = host host.memory.updateParams(self.params) - host.bridge.addMethod("inviteMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._invite) + host.bridge.addMethod( + "inviteMUC", ".plugin", in_sign="ssa{ss}s", out_sign="", method=self._invite + ) try: self.host.plugins[C.TEXT_CMDS].registerTextCommands(self) except KeyError: @@ -99,7 +106,7 @@ class XEP_0249(object): @param roomId: name of the room @param profile_key: %(doc_profile_key)s """ - #TODO: check parameters validity + # TODO: check parameters validity client = self.host.getClient(profile_key) self.invite(client, jid.JID(guest_jid_s), jid.JID(room_jid_s, options)) @@ -110,16 +117,16 @@ class XEP_0249(object): @param room(jid.JID): jid of the room where the user is invited @param options(dict): attribute with extra info (reason, password) as in #XEP-0249 """ - message = domish.Element((None, 'message')) + message = domish.Element((None, "message")) message["to"] = guest.full() - x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, 'x')) - x_elt['jid'] = room.userhost() + x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, "x")) + x_elt["jid"] = room.userhost() for key, value in options.iteritems(): - if key not in ('password', 'reason', 'thread'): + if key not in ("password", "reason", "thread"): log.warning(u"Ignoring invalid invite option: {}".format(key)) continue x_elt[key] = value - # there is not body in this message, so we can use directly send() + # there is not body in this message, so we can use directly send() client.send(message) def _accept(self, room_jid, profile_key=C.PROF_KEY_NONE): @@ -128,7 +135,10 @@ class XEP_0249(object): @param room (jid.JID): JID of the room """ client = self.host.getClient(profile_key) - log.info(_(u'Invitation accepted for room %(room)s [%(profile)s]') % {'room': room_jid.userhost(), 'profile': client.profile}) + log.info( + _(u"Invitation accepted for room %(room)s [%(profile)s]") + % {"room": room_jid.userhost(), "profile": client.profile} + ) d = self.host.plugins["XEP-0045"].join(client, room_jid, client.jid.user, {}) return d @@ -140,10 +150,13 @@ class XEP_0249(object): """ client = self.host.getClient(profile) try: - room_jid_s = message.firstChildElement()['jid'] - log.info(_(u'Invitation received for room %(room)s [%(profile)s]') % {'room': room_jid_s, 'profile': profile}) + room_jid_s = message.firstChildElement()["jid"] + log.info( + _(u"Invitation received for room %(room)s [%(profile)s]") + % {"room": room_jid_s, "profile": profile} + ) except: - log.error(_('Error while parsing invitation')) + log.error(_("Error while parsing invitation")) return from_jid_s = message["from"] room_jid = jid.JID(room_jid_s) @@ -152,21 +165,32 @@ class XEP_0249(object): except exceptions.NotFound: pass else: - log.info(_(u"Invitation silently discarded because user is already in the room.")) + log.info( + _(u"Invitation silently discarded because user is already in the room.") + ) return - autojoin = self.host.memory.getParamA(AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile) + autojoin = self.host.memory.getParamA( + AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile + ) if autojoin == "always": self._accept(room_jid, profile) elif autojoin == "never": - msg = D_("An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings.") % {'user': from_jid_s, 'room': room_jid_s} + msg = D_( + "An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings." + ) % {"user": from_jid_s, "room": room_jid_s} title = D_("MUC invitation") xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) else: # leave the default value here - confirm_msg = D_("You have been invited by %(user)s to join the room %(room)s. Do you accept?") % {'user': from_jid_s, 'room': room_jid_s} + confirm_msg = D_( + "You have been invited by %(user)s to join the room %(room)s. Do you accept?" + ) % {"user": from_jid_s, "room": room_jid_s} confirm_title = D_("MUC invitation") - d = xml_tools.deferConfirm(self.host, confirm_msg, confirm_title, profile=profile) + d = xml_tools.deferConfirm( + self.host, confirm_msg, confirm_title, profile=profile + ) + def accept_cb(accepted): if accepted: self._accept(room_jid, profile) @@ -184,7 +208,9 @@ class XEP_0249(object): try: contact_jid = jid.JID(contact_jid_s) except (RuntimeError, jid.InvalidFormat, AttributeError): - feedback = _(u"You must provide a valid JID to invite, like in '/invite contact@{host}'").format(host=my_host) + feedback = _( + u"You must provide a valid JID to invite, like in '/invite contact@{host}'" + ).format(host=my_host) self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) return False if not contact_jid.user: @@ -201,10 +227,14 @@ class XEP_0249_handler(XMPPHandler): self.host = plugin_parent.host def connectionInitialized(self): - self.xmlstream.addObserver(DIRECT_MUC_INVITATION_REQUEST, self.plugin_parent.onInvitation, profile=self.parent.profile) + self.xmlstream.addObserver( + DIRECT_MUC_INVITATION_REQUEST, + self.plugin_parent.onInvitation, + profile=self.parent.profile, + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_DIRECT_MUC_INVITATION)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0260.py b/sat/plugins/plugin_xep_0260.py index 9d91540b..1884efb7 100644 --- a/sat/plugins/plugin_xep_0260.py +++ b/sat/plugins/plugin_xep_0260.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -35,7 +36,7 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -NS_JINGLE_S5B = 'urn:xmpp:jingle:transports:s5b:1' +NS_JINGLE_S5B = "urn:xmpp:jingle:transports:s5b:1" PLUGIN_INFO = { C.PI_NAME: "Jingle SOCKS5 Bytestreams", @@ -44,15 +45,14 @@ PLUGIN_INFO = { C.PI_MODES: C.PLUG_MODE_BOTH, C.PI_PROTOCOLS: ["XEP-0260"], C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0065"], - C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback + C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback C.PI_MAIN: "XEP_0260", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams"""), } class ProxyError(Exception): - def __str__(self): return "an error happened while trying to use the proxy" @@ -63,8 +63,8 @@ class XEP_0260(object): def __init__(self, host): log.info(_("plugin Jingle SOCKS5 Bytestreams")) self.host = host - self._j = host.plugins["XEP-0166"] # shortcut to access jingle - self._s5b = host.plugins["XEP-0065"] # and socks5 bytestream + self._j = host.plugins["XEP-0166"] # shortcut to access jingle + self._s5b = host.plugins["XEP-0065"] # and socks5 bytestream try: self._jingle_ibb = host.plugins["XEP-0261"] except KeyError: @@ -81,14 +81,14 @@ class XEP_0260(object): @return (list[plugin_xep_0065.Candidate): list of parsed candidates """ candidates = [] - for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, 'candidate'): + for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, "candidate"): try: - cid = candidate_elt['cid'] - host = candidate_elt['host'] - jid_= jid.JID(candidate_elt['jid']) - port = int(candidate_elt.getAttribute('port', 1080)) - priority = int(candidate_elt['priority']) - type_ = candidate_elt.getAttribute('type', self._s5b.TYPE_DIRECT) + cid = candidate_elt["cid"] + host = candidate_elt["host"] + jid_ = jid.JID(candidate_elt["jid"]) + port = int(candidate_elt.getAttribute("port", 1080)) + priority = int(candidate_elt["priority"]) + type_ = candidate_elt.getAttribute("type", self._s5b.TYPE_DIRECT) except (KeyError, ValueError): raise exceptions.DataError() candidate = self._s5b.Candidate(host, port, type_, priority, jid_, cid) @@ -106,38 +106,51 @@ class XEP_0260(object): @param mode(str, None): 'tcp' or 'udp', or None to have no attribute @return (domish.Element): parent element where elements must be added """ - proxy = next((candidate for candidate in candidates if candidate.type == self._s5b.TYPE_PROXY), None) + proxy = next( + ( + candidate + for candidate in candidates + if candidate.type == self._s5b.TYPE_PROXY + ), + None, + ) transport_elt = domish.Element((NS_JINGLE_S5B, "transport")) - transport_elt['sid'] = sid + transport_elt["sid"] = sid if proxy is not None: - transport_elt['dstaddr'] = session_hash + transport_elt["dstaddr"] = session_hash if mode is not None: - transport_elt['mode'] = 'tcp' # XXX: we only manage tcp for now + transport_elt["mode"] = "tcp" # XXX: we only manage tcp for now for candidate in candidates: log.debug(u"Adding candidate: {}".format(candidate)) - candidate_elt = transport_elt.addElement('candidate', NS_JINGLE_S5B) + candidate_elt = transport_elt.addElement("candidate", NS_JINGLE_S5B) if candidate.id is None: candidate.id = unicode(uuid.uuid4()) - candidate_elt['cid'] = candidate.id - candidate_elt['host'] = candidate.host - candidate_elt['jid'] = candidate.jid.full() - candidate_elt['port'] = unicode(candidate.port) - candidate_elt['priority'] = unicode(candidate.priority) - candidate_elt['type'] = candidate.type + candidate_elt["cid"] = candidate.id + candidate_elt["host"] = candidate.host + candidate_elt["jid"] = candidate.jid.full() + candidate_elt["port"] = unicode(candidate.port) + candidate_elt["priority"] = unicode(candidate.priority) + candidate_elt["type"] = candidate.type return transport_elt @defer.inlineCallbacks def jingleSessionInit(self, client, session, content_name): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - sid = transport_data['sid'] = unicode(uuid.uuid4()) - session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid) - transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates - transport_data['stream_d'] = self._s5b.registerHash(client, session_hash, None) - candidates = transport_data['candidates'] = yield self._s5b.getCandidates(client) - mode = 'tcp' # XXX: we only manage tcp for now - transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client, mode) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + sid = transport_data["sid"] = unicode(uuid.uuid4()) + session_hash = transport_data["session_hash"] = self._s5b.getSessionHash( + client.jid, session["peer_jid"], sid + ) + transport_data["peer_session_hash"] = self._s5b.getSessionHash( + session["peer_jid"], client.jid, sid + ) # requester and target are inversed for peer candidates + transport_data["stream_d"] = self._s5b.registerHash(client, session_hash, None) + candidates = transport_data["candidates"] = yield self._s5b.getCandidates(client) + mode = "tcp" # XXX: we only manage tcp for now + transport_elt = self._buildCandidates( + session, candidates, sid, session_hash, client, mode + ) defer.returnValue(transport_elt) @@ -147,9 +160,11 @@ class XEP_0260(object): cf XEP-0260 § 2.4 """ # now that the proxy is activated, we have to inform other peer - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) - activated_elt = transport_elt.addElement('activated') - activated_elt['cid'] = candidate.id + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) + activated_elt = transport_elt.addElement("activated") + activated_elt["cid"] = candidate.id iq_elt.send() def _proxyActivatedEb(self, stanza_error, client, candidate, session, content_name): @@ -159,14 +174,21 @@ class XEP_0260(object): """ # TODO: fallback to IBB # now that the proxy is activated, we have to inform other peer - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) - transport_elt.addElement('proxy-error') + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) + transport_elt.addElement("proxy-error") iq_elt.send() - log.warning(u"Can't activate proxy, we need to fallback to IBB: {reason}" - .format(reason = stanza_error.value.condition)) + log.warning( + u"Can't activate proxy, we need to fallback to IBB: {reason}".format( + reason=stanza_error.value.condition + ) + ) self.doFallback(session, content_name, client) - def _foundPeerCandidate(self, candidate, session, transport_data, content_name, client): + def _foundPeerCandidate( + self, candidate, session, transport_data, content_name, client + ): """Called when the best candidate from other peer is found @param candidate(XEP_0065.Candidate, None): selected candidate, @@ -177,22 +199,24 @@ class XEP_0260(object): @param client(unicode): %(doc_client)s """ - transport_data['best_candidate'] = candidate + transport_data["best_candidate"] = candidate # we need to disconnect all non selected candidates before removing them - for c in transport_data['peer_candidates']: + for c in transport_data["peer_candidates"]: if c is None or c is candidate: continue c.discard() - del transport_data['peer_candidates'] - iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name) + del transport_data["peer_candidates"] + iq_elt, transport_elt = self._j.buildAction( + client, self._j.A_TRANSPORT_INFO, session, content_name + ) if candidate is None: log.warning(u"Can't connect to any peer candidate") - candidate_elt = transport_elt.addElement('candidate-error') + candidate_elt = transport_elt.addElement("candidate-error") else: log.info(u"Found best peer candidate: {}".format(unicode(candidate))) - candidate_elt = transport_elt.addElement('candidate-used') - candidate_elt['cid'] = candidate.id - iq_elt.send() # TODO: check result stanza + candidate_elt = transport_elt.addElement("candidate-used") + candidate_elt["cid"] = candidate.id + iq_elt.send() # TODO: check result stanza self._checkCandidates(session, content_name, transport_data, client) def _checkCandidates(self, session, content_name, transport_data, client): @@ -204,14 +228,14 @@ class XEP_0260(object): @param transport_data(dict): transport data @param client(unicode): %(doc_client)s """ - content_data = session['contents'][content_name] + content_data = session["contents"][content_name] try: - best_candidate = transport_data['best_candidate'] + best_candidate = transport_data["best_candidate"] except KeyError: # we have not our best candidate yet return try: - peer_best_candidate = transport_data['peer_best_candidate'] + peer_best_candidate = transport_data["peer_best_candidate"] except KeyError: # we have not peer best candidate yet return @@ -222,13 +246,17 @@ class XEP_0260(object): else: if best_candidate.priority == peer_best_candidate.priority: # same priority, we choose initiator one according to XEP-0260 §2.4 #4 - log.debug(u"Candidates have same priority, we select the one choosed by initiator") - if session['initiator'] == client.jid: + log.debug( + u"Candidates have same priority, we select the one choosed by initiator" + ) + if session["initiator"] == client.jid: choosed_candidate = best_candidate else: choosed_candidate = peer_best_candidate else: - choosed_candidate = max(best_candidate, peer_best_candidate, key=lambda c:c.priority) + choosed_candidate = max( + best_candidate, peer_best_candidate, key=lambda c: c.priority + ) if choosed_candidate is None: log.warning(u"Socks5 negociation failed, we need to fallback to IBB") @@ -241,34 +269,49 @@ class XEP_0260(object): # than also mean that best_candidate must be discarded ! try: best_candidate.discard() - except AttributeError: # but it can be None + except AttributeError: # but it can be None pass else: our_candidate = False - log.info(u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( - who = u'our' if our_candidate else u'other peer', - candidate = choosed_candidate)) - del transport_data['best_candidate'] - del transport_data['peer_best_candidate'] + log.info( + u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( + who=u"our" if our_candidate else u"other peer", + candidate=choosed_candidate, + ) + ) + del transport_data["best_candidate"] + del transport_data["peer_best_candidate"] if choosed_candidate.type == self._s5b.TYPE_PROXY: # the stream transfer need to wait for proxy activation # (see XEP-0260 § 2.4) if our_candidate: - d = self._s5b.connectCandidate(client, choosed_candidate, transport_data['session_hash']) - d.addCallback(lambda dummy: choosed_candidate.activate(transport_data['sid'], session['peer_jid'], client)) + d = self._s5b.connectCandidate( + client, choosed_candidate, transport_data["session_hash"] + ) + d.addCallback( + lambda dummy: choosed_candidate.activate( + transport_data["sid"], session["peer_jid"], client + ) + ) args = [client, choosed_candidate, session, content_name] - d.addCallbacks(self._proxyActivatedCb, self._proxyActivatedEb, args, None, args) + d.addCallbacks( + self._proxyActivatedCb, self._proxyActivatedEb, args, None, args + ) else: # this Deferred will be called when we'll receive activation confirmation from other peer - d = transport_data['activation_d'] = defer.Deferred() + d = transport_data["activation_d"] = defer.Deferred() else: d = defer.succeed(None) - if content_data['senders'] == session['role']: + if content_data["senders"] == session["role"]: # we can now start the stream transfer (or start it after proxy activation) - d.addCallback(lambda dummy: choosed_candidate.startTransfer(transport_data['session_hash'])) + d.addCallback( + lambda dummy: choosed_candidate.startTransfer( + transport_data["session_hash"] + ) + ) d.addErrback(self._startEb, session, content_name, client) def _startEb(self, fail, session, content_name, client): @@ -283,7 +326,9 @@ class XEP_0260(object): log.warning(u"Cant start transfert, we'll try fallback method: {}".format(reason)) self.doFallback(session, content_name, client) - def _candidateInfo(self, candidate_elt, session, content_name, transport_data, client): + def _candidateInfo( + self, candidate_elt, session, content_name, transport_data, client + ): """Called when best candidate has been received from peer (or if none is working) @param candidate_elt(domish.Element): candidate-used or candidate-error element @@ -293,34 +338,40 @@ class XEP_0260(object): @param transport_data(dict): transport data @param client(unicode): %(doc_client)s """ - if candidate_elt.name == 'candidate-error': + if candidate_elt.name == "candidate-error": # candidate-error, no candidate worked - transport_data['peer_best_candidate'] = None + transport_data["peer_best_candidate"] = None else: # candidate-used, one candidate was choosed try: - cid = candidate_elt.attributes['cid'] + cid = candidate_elt.attributes["cid"] except KeyError: log.warning(u"No cid found in ") raise exceptions.DataError try: - candidate = (c for c in transport_data['candidates'] if c.id == cid).next() + candidate = ( + c for c in transport_data["candidates"] if c.id == cid + ).next() except StopIteration: log.warning(u"Given cid doesn't correspond to any known candidate !") - raise exceptions.DataError # TODO: send an error to other peer, and use better exception + raise exceptions.DataError # TODO: send an error to other peer, and use better exception except KeyError: # a transport-info can also be intentionaly sent too early by other peer # but there is little probability - log.error(u'"candidates" key doesn\'t exists in transport_data, it should at this point') + log.error( + u'"candidates" key doesn\'t exists in transport_data, it should at this point' + ) raise exceptions.InternalError # at this point we have the candidate choosed by other peer - transport_data['peer_best_candidate'] = candidate + transport_data["peer_best_candidate"] = candidate log.info(u"Other peer best candidate: {}".format(candidate)) - del transport_data['candidates'] + del transport_data["candidates"] self._checkCandidates(session, content_name, transport_data, client) - def _proxyActivationInfo(self, proxy_elt, session, content_name, transport_data, client): + def _proxyActivationInfo( + self, proxy_elt, session, content_name, transport_data, client + ): """Called when proxy has been activated (or has sent an error) @param proxy_elt(domish.Element): or element @@ -331,81 +382,107 @@ class XEP_0260(object): @param client(unicode): %(doc_client)s """ try: - activation_d = transport_data.pop('activation_d') + activation_d = transport_data.pop("activation_d") except KeyError: log.warning(u"Received unexpected transport-info for proxy activation") - if proxy_elt.name == 'activated': + if proxy_elt.name == "activated": activation_d.callback(None) else: activation_d.errback(ProxyError()) @defer.inlineCallbacks def jingleHandler(self, client, action, session, content_name, transport_elt): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] if action in (self._j.A_ACCEPTED_ACK, self._j.A_PREPARE_RESPONDER): pass elif action == self._j.A_SESSION_ACCEPT: # initiator side, we select a candidate in the ones sent by responder - assert 'peer_candidates' not in transport_data - transport_data['peer_candidates'] = self._parseCandidates(transport_elt) + assert "peer_candidates" not in transport_data + transport_data["peer_candidates"] = self._parseCandidates(transport_elt) elif action == self._j.A_START: - session_hash = transport_data['session_hash'] - peer_candidates = transport_data['peer_candidates'] - stream_object = content_data['stream_object'] + session_hash = transport_data["session_hash"] + peer_candidates = transport_data["peer_candidates"] + stream_object = content_data["stream_object"] self._s5b.associateStreamObject(client, session_hash, stream_object) - stream_d = transport_data.pop('stream_d') - stream_d.chainDeferred(content_data['finished_d']) - peer_session_hash = transport_data['peer_session_hash'] - d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash) - d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client) + stream_d = transport_data.pop("stream_d") + stream_d.chainDeferred(content_data["finished_d"]) + peer_session_hash = transport_data["peer_session_hash"] + d = self._s5b.getBestCandidate( + client, peer_candidates, session_hash, peer_session_hash + ) + d.addCallback( + self._foundPeerCandidate, session, transport_data, content_name, client + ) elif action == self._j.A_SESSION_INITIATE: # responder side, we select a candidate in the ones sent by initiator # and we give our candidates - assert 'peer_candidates' not in transport_data - sid = transport_data['sid'] = transport_elt['sid'] - session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid) - peer_session_hash = transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates - peer_candidates = transport_data['peer_candidates'] = self._parseCandidates(transport_elt) - stream_object = content_data['stream_object'] + assert "peer_candidates" not in transport_data + sid = transport_data["sid"] = transport_elt["sid"] + session_hash = transport_data["session_hash"] = self._s5b.getSessionHash( + client.jid, session["peer_jid"], sid + ) + peer_session_hash = transport_data[ + "peer_session_hash" + ] = self._s5b.getSessionHash( + session["peer_jid"], client.jid, sid + ) # requester and target are inversed for peer candidates + peer_candidates = transport_data["peer_candidates"] = self._parseCandidates( + transport_elt + ) + stream_object = content_data["stream_object"] stream_d = self._s5b.registerHash(client, session_hash, stream_object) - stream_d.chainDeferred(content_data['finished_d']) - d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash) - d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client) + stream_d.chainDeferred(content_data["finished_d"]) + d = self._s5b.getBestCandidate( + client, peer_candidates, session_hash, peer_session_hash + ) + d.addCallback( + self._foundPeerCandidate, session, transport_data, content_name, client + ) candidates = yield self._s5b.getCandidates(client) # we remove duplicate candidates - candidates = [candidate for candidate in candidates if candidate not in peer_candidates] + candidates = [ + candidate for candidate in candidates if candidate not in peer_candidates + ] - transport_data['candidates'] = candidates + transport_data["candidates"] = candidates # we can now build a new element with our candidates - transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client) + transport_elt = self._buildCandidates( + session, candidates, sid, session_hash, client + ) elif action == self._j.A_TRANSPORT_INFO: # transport-info can be about candidate or proxy activation candidate_elt = None - for method, names in ((self._candidateInfo, ('candidate-used', 'candidate-error')), - (self._proxyActivationInfo, ('activated', 'proxy-error'))): + for method, names in ( + (self._candidateInfo, ("candidate-used", "candidate-error")), + (self._proxyActivationInfo, ("activated", "proxy-error")), + ): for name in names: try: candidate_elt = transport_elt.elements(NS_JINGLE_S5B, name).next() except StopIteration: continue else: - method(candidate_elt, session, content_name, transport_data, client) + method( + candidate_elt, session, content_name, transport_data, client + ) break if candidate_elt is None: - log.warning(u"Unexpected transport element: {}".format(transport_elt.toXml())) + log.warning( + u"Unexpected transport element: {}".format(transport_elt.toXml()) + ) elif action == self._j.A_DESTROY: # the transport is replaced (fallback ?), We need mainly to kill XEP-0065 session. # note that sid argument is not necessary for sessions created by this plugin - self._s5b.killSession(None, transport_data['session_hash'], None, client) + self._s5b.killSession(None, transport_data["session_hash"], None, client) else: log.warning(u"FIXME: unmanaged action {}".format(action)) @@ -415,9 +492,9 @@ class XEP_0260(object): if reason_elt.decline: log.debug(u"Session declined, deleting S5B session") # we just need to clean the S5B session if it is declined - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - self._s5b.killSession(None, transport_data['session_hash'], None, client) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + self._s5b.killSession(None, transport_data["session_hash"], None, client) def _doFallback(self, feature_checked, session, content_name, client): """Do the fallback, method called once feature is checked @@ -425,10 +502,14 @@ class XEP_0260(object): @param feature_checked(bool): True if other peer can do IBB """ if not feature_checked: - log.warning(u"Other peer can't manage jingle IBB, be have to terminate the session") + log.warning( + u"Other peer can't manage jingle IBB, be have to terminate the session" + ) self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) else: - self._j.transportReplace(client, self._jingle_ibb.NAMESPACE, session, content_name) + self._j.transportReplace( + client, self._jingle_ibb.NAMESPACE, session, content_name + ) def doFallback(self, session, content_name, client): """Fallback to IBB transport, used in last resort @@ -437,14 +518,18 @@ class XEP_0260(object): @param content_name(unicode): name of the current content @param client(unicode): %(doc_client)s """ - if session['role'] != self._j.ROLE_INITIATOR: + if session["role"] != self._j.ROLE_INITIATOR: # only initiator must do the fallback, see XEP-0260 §3 return if self._jingle_ibb is None: - log.warning(u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session") + log.warning( + u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session" + ) self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) else: - d = self.host.hasFeature(client, self._jingle_ibb.NAMESPACE, session['peer_jid']) + d = self.host.hasFeature( + client, self._jingle_ibb.NAMESPACE, session["peer_jid"] + ) d.addCallback(self._doFallback, session, content_name, client) return d @@ -452,8 +537,8 @@ class XEP_0260(object): class XEP_0260_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_S5B)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0261.py b/sat/plugins/plugin_xep_0261.py index 5390ee9d..1e89d35a 100644 --- a/sat/plugins/plugin_xep_0261.py +++ b/sat/plugins/plugin_xep_0261.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel from zope.interface import implements @@ -32,7 +33,7 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' +NS_JINGLE_IBB = "urn:xmpp:jingle:transports:ibb:1" PLUGIN_INFO = { C.PI_NAME: "Jingle In-Band Bytestreams", @@ -43,52 +44,58 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0047"], C.PI_MAIN: "XEP_0261", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams""") + C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams"""), } class XEP_0261(object): - NAMESPACE = NS_JINGLE_IBB # used by XEP-0260 plugin for transport-replace + NAMESPACE = NS_JINGLE_IBB # used by XEP-0260 plugin for transport-replace def __init__(self, host): log.info(_("plugin Jingle In-Band Bytestreams")) self.host = host - self._j = host.plugins["XEP-0166"] # shortcut to access jingle - self._ibb = host.plugins["XEP-0047"] # and in-band bytestream - self._j.registerTransport(NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000) # must be the lowest priority + self._j = host.plugins["XEP-0166"] # shortcut to access jingle + self._ibb = host.plugins["XEP-0047"] # and in-band bytestream + self._j.registerTransport( + NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000 + ) # must be the lowest priority def getHandler(self, client): return XEP_0261_handler() def jingleSessionInit(self, client, session, content_name): transport_elt = domish.Element((NS_JINGLE_IBB, "transport")) - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - transport_data['block_size'] = self._ibb.BLOCK_SIZE - transport_elt['block-size'] = unicode(transport_data['block_size']) - transport_elt['sid'] = transport_data['sid'] = unicode(uuid.uuid4()) + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + transport_data["block_size"] = self._ibb.BLOCK_SIZE + transport_elt["block-size"] = unicode(transport_data["block_size"]) + transport_elt["sid"] = transport_data["sid"] = unicode(uuid.uuid4()) return transport_elt def jingleHandler(self, client, action, session, content_name, transport_elt): - content_data = session['contents'][content_name] - transport_data = content_data['transport_data'] - if action in (self._j.A_SESSION_ACCEPT, - self._j.A_ACCEPTED_ACK, - self._j.A_TRANSPORT_ACCEPT): + content_data = session["contents"][content_name] + transport_data = content_data["transport_data"] + if action in ( + self._j.A_SESSION_ACCEPT, + self._j.A_ACCEPTED_ACK, + self._j.A_TRANSPORT_ACCEPT, + ): pass elif action in (self._j.A_SESSION_INITIATE, self._j.A_TRANSPORT_REPLACE): - transport_data['sid'] = transport_elt['sid'] + transport_data["sid"] = transport_elt["sid"] elif action in (self._j.A_START, self._j.A_PREPARE_RESPONDER): - peer_jid = session['peer_jid'] - sid = transport_data['sid'] - stream_object = content_data['stream_object'] + peer_jid = session["peer_jid"] + sid = transport_data["sid"] + stream_object = content_data["stream_object"] if action == self._j.A_START: - block_size = transport_data['block_size'] - d = self._ibb.startStream(client, stream_object, peer_jid, sid, block_size) - d.chainDeferred(content_data['finished_d']) + block_size = transport_data["block_size"] + d = self._ibb.startStream( + client, stream_object, peer_jid, sid, block_size + ) + d.chainDeferred(content_data["finished_d"]) else: d = self._ibb.createSession(client, stream_object, peer_jid, sid) - d.chainDeferred(content_data['finished_d']) + d.chainDeferred(content_data["finished_d"]) else: log.warning(u"FIXME: unmanaged action {}".format(action)) return transport_elt @@ -97,8 +104,8 @@ class XEP_0261(object): class XEP_0261_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_JINGLE_IBB)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0264.py b/sat/plugins/plugin_xep_0264.py index b60a5376..206fe1e1 100755 --- a/sat/plugins/plugin_xep_0264.py +++ b/sat/plugins/plugin_xep_0264.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.internet import threads from twisted.python.failure import Failure @@ -31,13 +32,17 @@ from wokkel import disco, iwokkel from sat.core import exceptions import hashlib + try: from PIL import Image except: - raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io") + raise exceptions.MissingModule( + u"Missing module pillow, please download/install it from https://python-pillow.github.io" + ) -# cf. https://stackoverflow.com/a/23575424 +# cf. https://stackoverflow.com/a/23575424 from PIL import ImageFile + ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -46,10 +51,10 @@ except ImportError: from wokkel.subprotocols import XMPPHandler -MIME_TYPE = u'image/jpeg' -SAVE_FORMAT = u'JPEG' # (cf. Pillow documentation) +MIME_TYPE = u"image/jpeg" +SAVE_FORMAT = u"JPEG" # (cf. Pillow documentation) -NS_THUMBS = 'urn:xmpp:thumbs:1' +NS_THUMBS = "urn:xmpp:thumbs:1" PLUGIN_INFO = { C.PI_NAME: "XEP-0264", @@ -60,7 +65,7 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0234"], C.PI_MAIN: "XEP_0264", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Thumbnails handling""") + C.PI_DESCRIPTION: _("""Thumbnails handling"""), } @@ -81,39 +86,39 @@ class XEP_0264(object): def _addFileThumbnails(self, file_elt, extra_args): try: - thumbnails = extra_args[u'extra'][C.KEY_THUMBNAILS] + thumbnails = extra_args[u"extra"][C.KEY_THUMBNAILS] except KeyError: return for thumbnail in thumbnails: - thumbnail_elt = file_elt.addElement((NS_THUMBS, u'thumbnail')) - thumbnail_elt['uri'] = u'cid:' + thumbnail['id'] - thumbnail_elt['media-type'] = MIME_TYPE - width, height = thumbnail['size'] - thumbnail_elt['width'] = unicode(width) - thumbnail_elt['height'] = unicode(height) + thumbnail_elt = file_elt.addElement((NS_THUMBS, u"thumbnail")) + thumbnail_elt["uri"] = u"cid:" + thumbnail["id"] + thumbnail_elt["media-type"] = MIME_TYPE + width, height = thumbnail["size"] + thumbnail_elt["width"] = unicode(width) + thumbnail_elt["height"] = unicode(height) return True def _getFileThumbnails(self, file_elt, file_data): thumbnails = [] - for thumbnail_elt in file_elt.elements(NS_THUMBS, u'thumbnail'): - uri = thumbnail_elt['uri'] - if uri.startswith ('cid:'): - thumbnail = {'id': uri[4:]} - width = thumbnail_elt.getAttribute('width') - height = thumbnail_elt.getAttribute('height') + for thumbnail_elt in file_elt.elements(NS_THUMBS, u"thumbnail"): + uri = thumbnail_elt["uri"] + if uri.startswith("cid:"): + thumbnail = {"id": uri[4:]} + width = thumbnail_elt.getAttribute("width") + height = thumbnail_elt.getAttribute("height") if width and height: try: - thumbnail['size'] = int(width), int(height) + thumbnail["size"] = int(width), int(height) except ValueError: pass try: - thumbnail['mime_type'] = thumbnail_elt['media-type'] + thumbnail["mime_type"] = thumbnail_elt["media-type"] except KeyError: pass thumbnails.append(thumbnail) if thumbnails: - file_data.setdefault('extra', {})[C.KEY_THUMBNAILS] = thumbnails + file_data.setdefault("extra", {})[C.KEY_THUMBNAILS] = thumbnails return True ## thumbnails generation ## @@ -145,11 +150,8 @@ class XEP_0264(object): uid = self.getThumbId(image_uid or source_path, size) with self.host.common_cache.cacheData( - PLUGIN_INFO[C.PI_IMPORT_NAME], - uid, - MIME_TYPE, - max_age, - ) as f: + PLUGIN_INFO[C.PI_IMPORT_NAME], uid, MIME_TYPE, max_age + ) as f: img.save(f, SAVE_FORMAT) return img.size, uid @@ -171,17 +173,19 @@ class XEP_0264(object): """ d = threads.deferToThread( - self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid) - d.addErrback(lambda failure_: - log.error(u"thumbnail generation error: {}".format(failure_))) + self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid + ) + d.addErrback( + lambda failure_: log.error(u"thumbnail generation error: {}".format(failure_)) + ) return d class XEP_0264_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_THUMBS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0277.py b/sat/plugins/plugin_xep_0277.py index 218693d4..75c9416a 100755 --- a/sat/plugins/plugin_xep_0277.py +++ b/sat/plugins/plugin_xep_0277.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from twisted.words.protocols.jabber import jid, error from twisted.words.protocols.jabber.xmlstream import XMPPHandler @@ -43,10 +44,10 @@ import dateutil import calendar import urlparse -NS_MICROBLOG = 'urn:xmpp:microblog:0' -NS_ATOM = 'http://www.w3.org/2005/Atom' +NS_MICROBLOG = "urn:xmpp:microblog:0" +NS_ATOM = "http://www.w3.org/2005/Atom" NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event") -NS_COMMENT_PREFIX = '{}:comments/'.format(NS_MICROBLOG) +NS_COMMENT_PREFIX = "{}:comments/".format(NS_MICROBLOG) PLUGIN_INFO = { @@ -58,7 +59,7 @@ PLUGIN_INFO = { C.PI_RECOMMENDATIONS: ["XEP-0059", "EXTRA-PEP"], C.PI_MAIN: "XEP_0277", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol""") + C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol"""), } @@ -72,49 +73,97 @@ class XEP_0277(object): def __init__(self, host): log.info(_(u"Microblogging plugin initialization")) self.host = host - host.registerNamespace('microblog', NS_MICROBLOG) - self._p = self.host.plugins["XEP-0060"] # this facilitate the access to pubsub plugin + host.registerNamespace("microblog", NS_MICROBLOG) + self._p = self.host.plugins[ + "XEP-0060" + ] # this facilitate the access to pubsub plugin self.rt_sessions = sat_defer.RTDeferredSessions() - self.host.plugins["XEP-0060"].addManagedNode(NS_MICROBLOG, items_cb=self._itemsReceived) + self.host.plugins["XEP-0060"].addManagedNode( + NS_MICROBLOG, items_cb=self._itemsReceived + ) - host.bridge.addMethod("mbSend", ".plugin", - in_sign='ssa{ss}s', out_sign='', - method=self._mbSend, - async=True) - host.bridge.addMethod("mbRetract", ".plugin", - in_sign='ssss', out_sign='', - method=self._mbRetract, - async=True) - host.bridge.addMethod("mbGet", ".plugin", - in_sign='ssiasa{ss}s', out_sign='(aa{ss}a{ss})', - method=self._mbGet, - async=True) - host.bridge.addMethod("mbSetAccess", ".plugin", in_sign='ss', out_sign='', - method=self.mbSetAccess, - async=True) - host.bridge.addMethod("mbSubscribeToMany", ".plugin", in_sign='sass', out_sign='s', - method=self._mbSubscribeToMany) - host.bridge.addMethod("mbGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssaa{ss}a{ss}))', - method=self._mbGetFromManyRTResult, async=True) - host.bridge.addMethod("mbGetFromMany", ".plugin", in_sign='sasia{ss}s', out_sign='s', - method=self._mbGetFromMany) - host.bridge.addMethod("mbGetFromManyWithCommentsRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))', - method=self._mbGetFromManyWithCommentsRTResult, async=True) - host.bridge.addMethod("mbGetFromManyWithComments", ".plugin", in_sign='sasiia{ss}a{ss}s', out_sign='s', - method=self._mbGetFromManyWithComments) + host.bridge.addMethod( + "mbSend", + ".plugin", + in_sign="ssa{ss}s", + out_sign="", + method=self._mbSend, + async=True, + ) + host.bridge.addMethod( + "mbRetract", + ".plugin", + in_sign="ssss", + out_sign="", + method=self._mbRetract, + async=True, + ) + host.bridge.addMethod( + "mbGet", + ".plugin", + in_sign="ssiasa{ss}s", + out_sign="(aa{ss}a{ss})", + method=self._mbGet, + async=True, + ) + host.bridge.addMethod( + "mbSetAccess", + ".plugin", + in_sign="ss", + out_sign="", + method=self.mbSetAccess, + async=True, + ) + host.bridge.addMethod( + "mbSubscribeToMany", + ".plugin", + in_sign="sass", + out_sign="s", + method=self._mbSubscribeToMany, + ) + host.bridge.addMethod( + "mbGetFromManyRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssaa{ss}a{ss}))", + method=self._mbGetFromManyRTResult, + async=True, + ) + host.bridge.addMethod( + "mbGetFromMany", + ".plugin", + in_sign="sasia{ss}s", + out_sign="s", + method=self._mbGetFromMany, + ) + host.bridge.addMethod( + "mbGetFromManyWithCommentsRTResult", + ".plugin", + in_sign="ss", + out_sign="(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))", + method=self._mbGetFromManyWithCommentsRTResult, + async=True, + ) + host.bridge.addMethod( + "mbGetFromManyWithComments", + ".plugin", + in_sign="sasiia{ss}a{ss}s", + out_sign="s", + method=self._mbGetFromManyWithComments, + ) def getHandler(self, client): return XEP_0277_handler() def _checkFeaturesCb(self, available): - return {'available': C.BOOL_TRUE} + return {"available": C.BOOL_TRUE} def _checkFeaturesEb(self, fail): - return {'available': C.BOOL_FALSE} + return {"available": C.BOOL_FALSE} def getFeatures(self, profile): client = self.host.getClient(profile) - d = self.host.checkFeatures(client, [], identity=('pubsub', 'pep')) + d = self.host.checkFeatures(client, [], identity=("pubsub", "pep")) d.addCallbacks(self._checkFeaturesCb, self._checkFeaturesEb) return d @@ -122,18 +171,27 @@ class XEP_0277(object): def _itemsReceived(self, client, itemsEvent): """Callback which manage items notifications (publish + retract)""" + def manageItem(data, event): - self.host.bridge.psEvent(C.PS_MICROBLOG, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, event, data, client.profile) + self.host.bridge.psEvent( + C.PS_MICROBLOG, + itemsEvent.sender.full(), + itemsEvent.nodeIdentifier, + event, + data, + client.profile, + ) for item in itemsEvent.items: if item.name == C.PS_ITEM: - self.item2mbdata(item).addCallbacks(manageItem, lambda failure: None, (C.PS_PUBLISH,)) + self.item2mbdata(item).addCallbacks( + manageItem, lambda failure: None, (C.PS_PUBLISH,) + ) elif item.name == C.PS_RETRACT: - manageItem({'id': item['id']}, C.PS_RETRACT) + manageItem({"id": item["id"]}, C.PS_RETRACT) else: raise exceptions.InternalError("Invalid event value") - ## data/item transformation ## @defer.inlineCallbacks @@ -156,13 +214,17 @@ class XEP_0277(object): """ if key in microblog_data: if not increment: - raise failure.Failure(exceptions.DataError("key {} is already present for item {}").format(key, item_elt['id'])) + raise failure.Failure( + exceptions.DataError( + "key {} is already present for item {}" + ).format(key, item_elt["id"]) + ) else: - idx=1 # the idx 0 is the key without suffix + idx = 1 # the idx 0 is the key without suffix fmt = "{}#{}" new_key = fmt.format(key, idx) while new_key in microblog_data: - idx+=1 + idx += 1 new_key = fmt.format(key, idx) key = new_key return key @@ -170,49 +232,62 @@ class XEP_0277(object): @defer.inlineCallbacks def parseElement(elem): """Parse title/content elements and fill microblog_data accordingly""" - type_ = elem.getAttribute('type') - if type_ == 'xhtml': + type_ = elem.getAttribute("type") + if type_ == "xhtml": data_elt = elem.firstChildElement() if data_elt is None: - raise failure.Failure(exceptions.DataError(u"XHML content not wrapped in a element, this is not standard !")) + raise failure.Failure( + exceptions.DataError( + u"XHML content not wrapped in a element, this is not standard !" + ) + ) if data_elt.uri != C.NS_XHTML: - raise failure.Failure(exceptions.DataError(_('Content of type XHTML must declare its namespace!'))) - key = check_conflict(u'{}_xhtml'.format(elem.name)) + raise failure.Failure( + exceptions.DataError( + _("Content of type XHTML must declare its namespace!") + ) + ) + key = check_conflict(u"{}_xhtml".format(elem.name)) data = data_elt.toXml() - microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML(data) + microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML( + data + ) else: key = check_conflict(elem.name) microblog_data[key] = unicode(elem) - - id_ = item_elt.getAttribute('id', '') # there can be no id for transient nodes - microblog_data[u'id'] = id_ + id_ = item_elt.getAttribute("id", "") # there can be no id for transient nodes + microblog_data[u"id"] = id_ if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT): - msg = u"Unsupported namespace {ns} in pubsub item {id_}".format(ns=item_elt.uri, id_=id_) + msg = u"Unsupported namespace {ns} in pubsub item {id_}".format( + ns=item_elt.uri, id_=id_ + ) log.warning(msg) raise failure.Failure(exceptions.DataError(msg)) try: - entry_elt = item_elt.elements(NS_ATOM, 'entry').next() + entry_elt = item_elt.elements(NS_ATOM, "entry").next() except StopIteration: - msg = u'No atom entry found in the pubsub item {}'.format(id_) + msg = u"No atom entry found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) # language try: - microblog_data[u'language'] = entry_elt[(C.NS_XML, u'lang')].strip() + microblog_data[u"language"] = entry_elt[(C.NS_XML, u"lang")].strip() except KeyError: pass # atom:id try: - id_elt = entry_elt.elements(NS_ATOM, 'id').next() + id_elt = entry_elt.elements(NS_ATOM, "id").next() except StopIteration: - msg = u'No atom id found in the pubsub item {}, this is not standard !'.format(id_) + msg = u"No atom id found in the pubsub item {}, this is not standard !".format( + id_ + ) log.warning(msg) - microblog_data[u'atom_id'] = "" + microblog_data[u"atom_id"] = "" else: - microblog_data[u'atom_id'] = unicode(id_elt) + microblog_data[u"atom_id"] = unicode(id_elt) # title/content(s) @@ -225,9 +300,9 @@ class XEP_0277(object): # except StopIteration: # msg = u'No atom title found in the pubsub item {}'.format(id_) # raise failure.Failure(exceptions.DataError(msg)) - title_elts = list(entry_elt.elements(NS_ATOM, 'title')) + title_elts = list(entry_elt.elements(NS_ATOM, "title")) if not title_elts: - msg = u'No atom title found in the pubsub item {}'.format(id_) + msg = u"No atom title found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) for title_elt in title_elts: yield parseElement(title_elt) @@ -235,113 +310,146 @@ class XEP_0277(object): # FIXME: as for , Atom only authorise at most 1 content # but XEP-0277 allows several ones. So for no we handle as # if more than one can be present - for content_elt in entry_elt.elements(NS_ATOM, 'content'): + for content_elt in entry_elt.elements(NS_ATOM, "content"): yield parseElement(content_elt) # we check that text content is present - for key in ('title', 'content'): - if key not in microblog_data and ('{}_xhtml'.format(key)) in microblog_data: - log.warning(u"item {id_} provide a {key}_xhtml data but not a text one".format(id_=id_, key=key)) + for key in ("title", "content"): + if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data: + log.warning( + u"item {id_} provide a {key}_xhtml data but not a text one".format( + id_=id_, key=key + ) + ) # ... and do the conversion if it's not - microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].\ - convert(microblog_data[u'{}_xhtml'.format(key)], - self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, - self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, - False) + microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].convert( + microblog_data[u"{}_xhtml".format(key)], + self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, + self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, + False, + ) - if 'content' not in microblog_data: + if "content" not in microblog_data: # use the atom title data as the microblog body content - microblog_data[u'content'] = microblog_data[u'title'] - del microblog_data[u'title'] - if 'title_xhtml' in microblog_data: - microblog_data[u'content_xhtml'] = microblog_data[u'title_xhtml'] - del microblog_data[u'title_xhtml'] + microblog_data[u"content"] = microblog_data[u"title"] + del microblog_data[u"title"] + if "title_xhtml" in microblog_data: + microblog_data[u"content_xhtml"] = microblog_data[u"title_xhtml"] + del microblog_data[u"title_xhtml"] # published/updated dates try: - updated_elt = entry_elt.elements(NS_ATOM, 'updated').next() + updated_elt = entry_elt.elements(NS_ATOM, "updated").next() except StopIteration: - msg = u'No atom updated element found in the pubsub item {}'.format(id_) + msg = u"No atom updated element found in the pubsub item {}".format(id_) raise failure.Failure(exceptions.DataError(msg)) - microblog_data[u'updated'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple())) + microblog_data[u"updated"] = unicode( + calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple()) + ) try: - published_elt = entry_elt.elements(NS_ATOM, 'published').next() + published_elt = entry_elt.elements(NS_ATOM, "published").next() except StopIteration: - microblog_data[u'published'] = microblog_data[u'updated'] + microblog_data[u"published"] = microblog_data[u"updated"] else: - microblog_data[u'published'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(published_elt)).utctimetuple())) + microblog_data[u"published"] = unicode( + calendar.timegm( + dateutil.parser.parse(unicode(published_elt)).utctimetuple() + ) + ) # links - for link_elt in entry_elt.elements(NS_ATOM, 'link'): - if link_elt.getAttribute('rel') == 'replies' and link_elt.getAttribute('title') == 'comments': - key = check_conflict('comments', True) - microblog_data[key] = link_elt['href'] + for link_elt in entry_elt.elements(NS_ATOM, "link"): + if ( + link_elt.getAttribute("rel") == "replies" + and link_elt.getAttribute("title") == "comments" + ): + key = check_conflict("comments", True) + microblog_data[key] = link_elt["href"] try: service, node = self.parseCommentUrl(microblog_data[key]) except: log.warning(u"Can't parse url {}".format(microblog_data[key])) del microblog_data[key] else: - microblog_data[u'{}_service'.format(key)] = service.full() - microblog_data[u'{}_node'.format(key)] = node + microblog_data[u"{}_service".format(key)] = service.full() + microblog_data[u"{}_node".format(key)] = node else: - rel = link_elt.getAttribute('rel','') - title = link_elt.getAttribute('title','') - href = link_elt.getAttribute('href','') - log.warning(u"Unmanaged link element: rel={rel} title={title} href={href}".format(rel=rel, title=title, href=href)) + rel = link_elt.getAttribute("rel", "") + title = link_elt.getAttribute("title", "") + href = link_elt.getAttribute("href", "") + log.warning( + u"Unmanaged link element: rel={rel} title={title} href={href}".format( + rel=rel, title=title, href=href + ) + ) # author try: - author_elt = entry_elt.elements(NS_ATOM, 'author').next() + author_elt = entry_elt.elements(NS_ATOM, "author").next() except StopIteration: log.debug(u"Can't find author element in item {}".format(id_)) else: publisher = item_elt.getAttribute("publisher") # name try: - name_elt = author_elt.elements(NS_ATOM, 'name').next() + name_elt = author_elt.elements(NS_ATOM, "name").next() except StopIteration: - log.warning(u"No name element found in author element of item {}".format(id_)) + log.warning( + u"No name element found in author element of item {}".format(id_) + ) else: - microblog_data[u'author'] = unicode(name_elt) + microblog_data[u"author"] = unicode(name_elt) # uri try: - uri_elt = author_elt.elements(NS_ATOM, 'uri').next() + uri_elt = author_elt.elements(NS_ATOM, "uri").next() except StopIteration: - log.debug(u"No uri element found in author element of item {}".format(id_)) + log.debug( + u"No uri element found in author element of item {}".format(id_) + ) if publisher: - microblog_data[u'author_jid'] = publisher + microblog_data[u"author_jid"] = publisher else: uri = unicode(uri_elt) if uri.startswith("xmpp:"): uri = uri[5:] - microblog_data[u'author_jid'] = uri + microblog_data[u"author_jid"] = uri else: - microblog_data[u'author_jid'] = item_elt.getAttribute(u"publisher") or "" + microblog_data[u"author_jid"] = ( + item_elt.getAttribute(u"publisher") or "" + ) if not publisher: log.debug(u"No publisher attribute, we can't verify author jid") - microblog_data[u'author_jid_verified'] = C.BOOL_FALSE + microblog_data[u"author_jid_verified"] = C.BOOL_FALSE elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID(): - microblog_data[u'author_jid_verified'] = C.BOOL_TRUE + microblog_data[u"author_jid_verified"] = C.BOOL_TRUE else: - log.warning(u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(uri, item_elt.getAttribute("publisher"))) - microblog_data[u'author_jid_verified'] = C.BOOL_FALSE + log.warning( + u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format( + uri, item_elt.getAttribute("publisher") + ) + ) + microblog_data[u"author_jid_verified"] = C.BOOL_FALSE # email try: - email_elt = author_elt.elements(NS_ATOM, 'email').next() + email_elt = author_elt.elements(NS_ATOM, "email").next() except StopIteration: pass else: - microblog_data[u'author_email'] = unicode(email_elt) + microblog_data[u"author_email"] = unicode(email_elt) # categories - categories = (category_elt.getAttribute('term','') for category_elt in entry_elt.elements(NS_ATOM, 'category')) - data_format.iter2dict('tag', categories, microblog_data) + categories = ( + category_elt.getAttribute("term", "") + for category_elt in entry_elt.elements(NS_ATOM, "category") + ) + data_format.iter2dict("tag", categories, microblog_data) ## the trigger ## # if other plugins have things to add or change - yield self.host.trigger.point("XEP-0277_item2data", item_elt, entry_elt, microblog_data) + yield self.host.trigger.point( + "XEP-0277_item2data", item_elt, entry_elt, microblog_data + ) defer.returnValue(microblog_data) @@ -357,115 +465,144 @@ class XEP_0277(object): Needed to construct Atom id @return: deferred which fire domish.Element """ - entry_elt = domish.Element((NS_ATOM, 'entry')) + entry_elt = domish.Element((NS_ATOM, "entry")) ## language ## - if u'language' in data: - entry_elt[(C.NS_XML, u'lang')] = data[u'language'].strip() + if u"language" in data: + entry_elt[(C.NS_XML, u"lang")] = data[u"language"].strip() ## content and title ## synt = self.host.plugins["TEXT-SYNTAXES"] - for elem_name in ('title', 'content'): - for type_ in ['', '_rich', '_xhtml']: + for elem_name in ("title", "content"): + for type_ in ["", "_rich", "_xhtml"]: attr = "{}{}".format(elem_name, type_) if attr in data: elem = entry_elt.addElement(elem_name) if type_: - if type_ == '_rich': # convert input from current syntax to XHTML - xml_content = yield synt.convert(data[attr], synt.getCurrentSyntax(client.profile), "XHTML") - if '{}_xhtml'.format(elem_name) in data: - raise failure.Failure(exceptions.DataError(_("Can't have xhtml and rich content at the same time"))) + if type_ == "_rich": # convert input from current syntax to XHTML + xml_content = yield synt.convert( + data[attr], synt.getCurrentSyntax(client.profile), "XHTML" + ) + if "{}_xhtml".format(elem_name) in data: + raise failure.Failure( + exceptions.DataError( + _( + "Can't have xhtml and rich content at the same time" + ) + ) + ) else: xml_content = data[attr] - div_elt = xml_tools.ElementParser()(xml_content, namespace=C.NS_XHTML) - if div_elt.name != 'div' or div_elt.uri != C.NS_XHTML or div_elt.attributes: + div_elt = xml_tools.ElementParser()( + xml_content, namespace=C.NS_XHTML + ) + if ( + div_elt.name != "div" + or div_elt.uri != C.NS_XHTML + or div_elt.attributes + ): # we need a wrapping at the top with XHTML namespace - wrap_div_elt = domish.Element((C.NS_XHTML, 'div')) + wrap_div_elt = domish.Element((C.NS_XHTML, "div")) wrap_div_elt.addChild(div_elt) div_elt = wrap_div_elt elem.addChild(div_elt) - elem['type'] = 'xhtml' + elem["type"] = "xhtml" if elem_name not in data: # there is raw text content, which is mandatory # so we create one from xhtml content elem_txt = entry_elt.addElement(elem_name) - text_content = yield self.host.plugins["TEXT-SYNTAXES"].convert(xml_content, + text_content = yield self.host.plugins[ + "TEXT-SYNTAXES" + ].convert( + xml_content, self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, - False) + False, + ) elem_txt.addContent(text_content) - elem_txt['type'] = 'text' + elem_txt["type"] = "text" else: # raw text only needs to be escaped to get HTML-safe sequence elem.addContent(data[attr]) - elem['type'] = 'text' + elem["type"] = "text" try: - entry_elt.elements(NS_ATOM, 'title').next() + entry_elt.elements(NS_ATOM, "title").next() except StopIteration: # we have no title element which is mandatory # so we transform content element to title - elems = list(entry_elt.elements(NS_ATOM, 'content')) + elems = list(entry_elt.elements(NS_ATOM, "content")) if not elems: - raise exceptions.DataError("There must be at least one content or title element") + raise exceptions.DataError( + "There must be at least one content or title element" + ) for elem in elems: - elem.name = 'title' + elem.name = "title" ## author ## - author_elt = entry_elt.addElement('author') + author_elt = entry_elt.addElement("author") try: - author_name = data['author'] + author_name = data["author"] except KeyError: # FIXME: must use better name author_name = client.jid.user - author_elt.addElement('name', content=author_name) + author_elt.addElement("name", content=author_name) try: - author_jid_s = data['author_jid'] + author_jid_s = data["author_jid"] except KeyError: author_jid_s = client.jid.userhost() - author_elt.addElement('uri', content="xmpp:{}".format(author_jid_s)) + author_elt.addElement("uri", content="xmpp:{}".format(author_jid_s)) try: - author_jid_s = data['author_email'] + author_jid_s = data["author_email"] except KeyError: pass ## published/updated time ## current_time = time.time() - entry_elt.addElement('updated', - content = utils.xmpp_date(float(data.get('updated', current_time)))) - entry_elt.addElement('published', - content = utils.xmpp_date(float(data.get('published', current_time)))) + entry_elt.addElement( + "updated", content=utils.xmpp_date(float(data.get("updated", current_time))) + ) + entry_elt.addElement( + "published", + content=utils.xmpp_date(float(data.get("published", current_time))), + ) ## categories ## for tag in data_format.dict2iter("tag", data): category_elt = entry_elt.addElement("category") - category_elt['term'] = tag + category_elt["term"] = tag ## id ## - entry_id = data.get('id', xmpp_uri.buildXMPPUri( - u'pubsub', - path=service.full() if service is not None else client.jid.userhost(), - node=node, - item=item_id)) - entry_elt.addElement('id', content=entry_id) # + entry_id = data.get( + "id", + xmpp_uri.buildXMPPUri( + u"pubsub", + path=service.full() if service is not None else client.jid.userhost(), + node=node, + item=item_id, + ), + ) + entry_elt.addElement("id", content=entry_id) # ## comments ## - if 'comments' in data: - link_elt = entry_elt.addElement('link') - link_elt['href'] = data['comments'] - link_elt['rel'] = 'replies' - link_elt['title'] = 'comments' + if "comments" in data: + link_elt = entry_elt.addElement("link") + link_elt["href"] = data["comments"] + link_elt["rel"] = "replies" + link_elt["title"] = "comments" ## final item building ## item_elt = pubsub.Item(id=item_id, payload=entry_elt) ## the trigger ## # if other plugins have things to add or change - yield self.host.trigger.point("XEP-0277_data2entry", client, data, entry_elt, item_elt) + yield self.host.trigger.point( + "XEP-0277_data2entry", client, data, entry_elt, item_elt + ) defer.returnValue(item_elt) @@ -489,17 +626,21 @@ class XEP_0277(object): if parent_service.user: # we are on a PEP if parent_service.host == client.jid.host: - # it's our server, we use already found client.pubsub_service below + # it's our server, we use already found client.pubsub_service below pass else: # other server, let's try to find a non PEP service there - d = self.host.findServiceEntity(client, "pubsub", "service", parent_service) + d = self.host.findServiceEntity( + client, "pubsub", "service", parent_service + ) d.addCallback(lambda entity: entity or parent_service) else: # parent is already on a normal Pubsub service, we re-use it return defer.succeed(parent_service) - return defer.succeed(client.pubsub_service if client.pubsub_service is not None else parent_service) + return defer.succeed( + client.pubsub_service if client.pubsub_service is not None else parent_service + ) @defer.inlineCallbacks def _manageComments(self, client, mb_data, service, node, item_id, access=None): @@ -517,9 +658,13 @@ class XEP_0277(object): # FIXME: if 'comments' already exists in mb_data, it is not used to create the Node allow_comments = C.bool(mb_data.pop("allow_comments", "false")) if not allow_comments: - if 'comments' in mb_data: - log.warning(u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format(uri=mb_data['comments'])) - del mb_data['comments'] + if "comments" in mb_data: + log.warning( + u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format( + uri=mb_data["comments"] + ) + ) + del mb_data["comments"] return if access is None: @@ -527,60 +672,75 @@ class XEP_0277(object): parent_node_config = yield self._p.getConfiguration(client, service, node) access = parent_node_config.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN) - options = {self._p.OPT_ACCESS_MODEL: access, - self._p.OPT_PERSIST_ITEMS: 1, - self._p.OPT_MAX_ITEMS: -1, - self._p.OPT_DELIVER_PAYLOADS: 1, - self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, - # FIXME: would it make sense to restrict publish model to subscribers? - self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, - } + options = { + self._p.OPT_ACCESS_MODEL: access, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + # FIXME: would it make sense to restrict publish model to subscribers? + self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN, + } # if other plugins need to change the options yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options) try: - comments_node = mb_data['comments_node'] + comments_node = mb_data["comments_node"] except KeyError: comments_node = self.getCommentsNode(item_id) else: if not comments_node: - raise exceptions.DataError(u"if comments_node is present, it must not be empty") + raise exceptions.DataError( + u"if comments_node is present, it must not be empty" + ) try: - comments_service = jid.JID(mb_data['comments_service']) + comments_service = jid.JID(mb_data["comments_service"]) except KeyError: comments_service = yield self.getCommentsService(client, service) try: yield self._p.createNode(client, comments_service, comments_node, options) except error.StanzaError as e: - if e.condition == 'conflict': - log.info(u"node {} already exists on service {}".format(comments_node, comments_service)) + if e.condition == "conflict": + log.info( + u"node {} already exists on service {}".format( + comments_node, comments_service + ) + ) else: raise e else: if access == self._p.ACCESS_WHITELIST: # for whitelist access we need to copy affiliations from parent item - comments_affiliations = yield self._p.getNodeAffiliations(client, service, node) + comments_affiliations = yield self._p.getNodeAffiliations( + client, service, node + ) # …except for "member", that we transform to publisher # because we wants members to be able to write to comments for jid_, affiliation in comments_affiliations.items(): - if affiliation == 'member': - comments_affiliations[jid_] == 'publisher' + if affiliation == "member": + comments_affiliations[jid_] == "publisher" - yield self._p.setNodeAffiliations(client, comments_service, comments_node, comments_affiliations) + yield self._p.setNodeAffiliations( + client, comments_service, comments_node, comments_affiliations + ) if comments_service is None: comments_service = client.jid.userhostJID() - if 'comments' in mb_data: - if not mb_data['comments']: - raise exceptions.DataError(u"if comments is present, it must not be empty") - if 'comments_node' in mb_data or 'comments_service' in mb_data: - raise exceptions.DataError(u"You can't use comments_service/comments_node and comments at the same time") + if "comments" in mb_data: + if not mb_data["comments"]: + raise exceptions.DataError( + u"if comments is present, it must not be empty" + ) + if "comments_node" in mb_data or "comments_service" in mb_data: + raise exceptions.DataError( + u"You can't use comments_service/comments_node and comments at the same time" + ) else: - mb_data['comments'] = self._p.getNodeURI(comments_service, comments_node) + mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node) def _mbSend(self, service, node, data, profile_key): service = jid.JID(service) if service else None @@ -604,7 +764,7 @@ class XEP_0277(object): if node is None: node = NS_MICROBLOG - item_id = data.get('id') or unicode(shortuuid.uuid()) + item_id = data.get("id") or unicode(shortuuid.uuid()) try: yield self._manageComments(client, data, service, node, item_id, access=None) @@ -618,11 +778,25 @@ class XEP_0277(object): def _mbRetract(self, service_jid_s, nodeIdentifier, itemIdentifier, profile_key): """Call self._p._retractItem, but use default node if node is empty""" - return self._p._retractItem(service_jid_s, nodeIdentifier or NS_MICROBLOG, itemIdentifier, True, profile_key) + return self._p._retractItem( + service_jid_s, + nodeIdentifier or NS_MICROBLOG, + itemIdentifier, + True, + profile_key, + ) ## get ## - def _mbGet(self, service='', node='', max_items=10, item_ids=None, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGet( + self, + service="", + node="", + max_items=10, + item_ids=None, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @param item_ids (list[unicode]): list of item IDs @@ -631,11 +805,27 @@ class XEP_0277(object): service = jid.JID(service) if service else None max_items = None if max_items == C.NO_LIMIT else max_items extra = self._p.parseExtra(extra_dict) - return self.mbGet(client, service, node or None, max_items, item_ids, extra.rsm_request, extra.extra) - + return self.mbGet( + client, + service, + node or None, + max_items, + item_ids, + extra.rsm_request, + extra.extra, + ) @defer.inlineCallbacks - def mbGet(self, client, service=None, node=None, max_items=10, item_ids=None, rsm_request=None, extra=None): + def mbGet( + self, + client, + service=None, + node=None, + max_items=10, + item_ids=None, + rsm_request=None, + extra=None, + ): """Get some microblogs @param service(jid.JID, None): jid of the publisher @@ -650,7 +840,15 @@ class XEP_0277(object): """ if node is None: node = NS_MICROBLOG - items_data = yield self._p.getItems(client, service, node, max_items=max_items, item_ids=item_ids, rsm_request=rsm_request, extra=extra) + items_data = yield self._p.getItems( + client, + service, + node, + max_items=max_items, + item_ids=item_ids, + rsm_request=rsm_request, + extra=extra, + ) serialised = yield self._p.serItemsDataD(items_data, self.item2mbdata) defer.returnValue(serialised) @@ -663,13 +861,13 @@ class XEP_0277(object): will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net') @return (tuple[jid.JID, unicode]): service and node """ - parsed_url = urlparse.urlparse(node_url, 'xmpp') + parsed_url = urlparse.urlparse(node_url, "xmpp") service = jid.JID(parsed_url.path) - parsed_queries = urlparse.parse_qs(parsed_url.query.encode('utf-8')) - node = parsed_queries.get('node', [''])[0].decode('utf-8') + parsed_queries = urlparse.parse_qs(parsed_url.query.encode("utf-8")) + node = parsed_queries.get("node", [""])[0].decode("utf-8") if not node: - raise failure.Failure(exceptions.DataError('Invalid comments link')) + raise failure.Failure(exceptions.DataError("Invalid comments link")) return (service, node) @@ -682,35 +880,49 @@ class XEP_0277(object): @param access: Node access model, according to xep-0060 #4.5 @param profile_key: profile key """ - # FIXME: check if this mehtod is need, deprecate it if not + # FIXME: check if this mehtod is need, deprecate it if not client = self.host.getClient(profile_key) - _options = {self._p.OPT_ACCESS_MODEL: access, self._p.OPT_PERSIST_ITEMS: 1, self._p.OPT_MAX_ITEMS: -1, self._p.OPT_DELIVER_PAYLOADS: 1, self._p.OPT_SEND_ITEM_SUBSCRIBE: 1} + _options = { + self._p.OPT_ACCESS_MODEL: access, + self._p.OPT_PERSIST_ITEMS: 1, + self._p.OPT_MAX_ITEMS: -1, + self._p.OPT_DELIVER_PAYLOADS: 1, + self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, + } def cb(result): - #Node is created with right permission + # Node is created with right permission log.debug(_(u"Microblog node has now access %s") % access) def fatal_err(s_error): - #Something went wrong + # Something went wrong log.error(_(u"Can't set microblog access")) raise NodeAccessChangeException() def err_cb(s_error): - #If the node already exists, the condition is "conflict", - #else we have an unmanaged error - if s_error.value.condition == 'conflict': - #d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG) - #d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err)) + # If the node already exists, the condition is "conflict", + # else we have an unmanaged error + if s_error.value.condition == "conflict": + # d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG) + # d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err)) change_node_options().addCallback(cb).addErrback(fatal_err) else: fatal_err(s_error) def create_node(): - return self._p.createNode(client, client.jid.userhostJID(), NS_MICROBLOG, _options) + return self._p.createNode( + client, client.jid.userhostJID(), NS_MICROBLOG, _options + ) def change_node_options(): - return self._p.setOptions(client.jid.userhostJID(), NS_MICROBLOG, client.jid.userhostJID(), _options, profile_key=profile_key) + return self._p.setOptions( + client.jid.userhostJID(), + NS_MICROBLOG, + client.jid.userhostJID(), + _options, + profile_key=profile_key, + ) create_node().addCallback(cb).addErrback(err_cb) @@ -735,12 +947,17 @@ class XEP_0277(object): jids_set = client.roster.getJidsSet(publishers_type, publishers) if publishers_type == C.ALL: try: # display messages from salut-a-toi@libervia.org or other PEP services - services = self.host.plugins["EXTRA-PEP"].getFollowedEntities(profile_key) + services = self.host.plugins["EXTRA-PEP"].getFollowedEntities( + profile_key + ) except KeyError: pass # plugin is not loaded else: if services: - log.debug("Extra PEP followed entities: %s" % ", ".join([unicode(service) for service in services])) + log.debug( + "Extra PEP followed entities: %s" + % ", ".join([unicode(service) for service in services]) + ) jids_set.update(services) node_data = [] @@ -757,7 +974,11 @@ class XEP_0277(object): """ if publishers_type == C.ALL: if publishers: - raise failure.Failure(ValueError("Can't use publishers with {} type".format(publishers_type))) + raise failure.Failure( + ValueError( + "Can't use publishers with {} type".format(publishers_type) + ) + ) else: publishers = None elif publishers_type == C.JID: @@ -785,8 +1006,12 @@ class XEP_0277(object): @param profile: %(doc_profile)s @return (str): session id """ - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) - return self._p.subscribeToMany(node_data, client.jid.userhostJID(), profile_key=profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) + return self._p.subscribeToMany( + node_data, client.jid.userhostJID(), profile_key=profile_key + ) # get # @@ -804,32 +1029,65 @@ class XEP_0277(object): - items_metadata(dict): metadata as returned by [mbGet] @param profile_key: %(doc_profile_key)s """ + def onSuccess(items_data): """convert items elements to list of microblog data in items_data""" d = self._p.serItemsDataD(items_data, self.item2mbdata) - d.addCallback(lambda serialised:('', serialised)) + d.addCallback(lambda serialised: ("", serialised)) return d profile = self.host.getClient(profile_key).profile - d = self._p.getRTResults(session_id, - on_success = onSuccess, - on_error = lambda failure: (unicode(failure.value), ([],{})), - profile = profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d = self._p.getRTResults( + session_id, + on_success=onSuccess, + on_error=lambda failure: (unicode(failure.value), ([], {})), + profile=profile, + ) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _mbGetFromMany(self, publishers_type, publishers, max_items=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGetFromMany( + self, + publishers_type, + publishers, + max_items=10, + extra_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit """ max_items = None if max_items == C.NO_LIMIT else max_items publishers_type, publishers = self._checkPublishers(publishers_type, publishers) extra = self._p.parseExtra(extra_dict) - return self.mbGetFromMany(publishers_type, publishers, max_items, extra.rsm_request, extra.extra, profile_key) + return self.mbGetFromMany( + publishers_type, + publishers, + max_items, + extra.rsm_request, + extra.extra, + profile_key, + ) - def mbGetFromMany(self, publishers_type, publishers, max_items=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): + def mbGetFromMany( + self, + publishers_type, + publishers, + max_items=None, + rsm_request=None, + extra=None, + profile_key=C.PROF_KEY_NONE, + ): """Get the published microblogs for a list of groups or jids @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") @@ -841,12 +1099,18 @@ class XEP_0277(object): @return (str): RT Deferred session id """ # XXX: extra is unused here so far - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) - return self._p.getFromMany(node_data, max_items, rsm_request, profile_key=profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) + return self._p.getFromMany( + node_data, max_items, rsm_request, profile_key=profile_key + ) # comments # - def _mbGetFromManyWithCommentsRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): + def _mbGetFromManyWithCommentsRTResult( + self, session_id, profile_key=C.PROF_KEY_DEFAULT + ): """Get real-time results for [mbGetFromManyWithComments] session @param session_id: id of the real-time deferred session @@ -869,12 +1133,29 @@ class XEP_0277(object): """ profile = self.host.getClient(profile_key).profile d = self.rt_sessions.getResults(session_id, profile=profile) - d.addCallback(lambda ret: (ret[0], - [(service.full(), node, failure, items, metadata) - for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) + d.addCallback( + lambda ret: ( + ret[0], + [ + (service.full(), node, failure, items, metadata) + for (service, node), (success, (failure, (items, metadata))) in ret[ + 1 + ].iteritems() + ], + ) + ) return d - def _mbGetFromManyWithComments(self, publishers_type, publishers, max_items=10, max_comments=C.NO_LIMIT, extra_dict=None, extra_comments_dict=None, profile_key=C.PROF_KEY_NONE): + def _mbGetFromManyWithComments( + self, + publishers_type, + publishers, + max_items=10, + max_comments=C.NO_LIMIT, + extra_dict=None, + extra_comments_dict=None, + profile_key=C.PROF_KEY_NONE, + ): """ @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit @param max_comments(int): maximum number of comments to get, C.NO_LIMIT for no limit @@ -884,14 +1165,30 @@ class XEP_0277(object): publishers_type, publishers = self._checkPublishers(publishers_type, publishers) extra = self._p.parseExtra(extra_dict) extra_comments = self._p.parseExtra(extra_comments_dict) - return self.mbGetFromManyWithComments(publishers_type, publishers, max_items, max_comments or None, - extra.rsm_request, - extra.extra, - extra_comments.rsm_request, - extra_comments.extra, - profile_key) + return self.mbGetFromManyWithComments( + publishers_type, + publishers, + max_items, + max_comments or None, + extra.rsm_request, + extra.extra, + extra_comments.rsm_request, + extra_comments.extra, + profile_key, + ) - def mbGetFromManyWithComments(self, publishers_type, publishers, max_items=None, max_comments=None, rsm_request=None, extra=None, rsm_comments=None, extra_comments=None, profile_key=C.PROF_KEY_NONE): + def mbGetFromManyWithComments( + self, + publishers_type, + publishers, + max_items=None, + max_comments=None, + rsm_request=None, + extra=None, + rsm_comments=None, + extra_comments=None, + profile_key=C.PROF_KEY_NONE, + ): """Helper method to get the microblogs and their comments in one shot @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") @@ -909,7 +1206,9 @@ class XEP_0277(object): # to serialise and associate the data, but it make life in frontends side # a lot easier - client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) + client, node_data = self._getClientAndNodeData( + publishers_type, publishers, profile_key + ) def getComments(items_data): """Retrieve comments and add them to the items_data @@ -919,29 +1218,50 @@ class XEP_0277(object): with a list of comments data (service, node, list of items, metadata) """ items, metadata = items_data - items_dlist = [] # deferred list for items + items_dlist = [] # deferred list for items for item in items: - dlist = [] # deferred list for comments + dlist = [] # deferred list for comments for key, value in item.iteritems(): # we look for comments - if key.startswith('comments') and key.endswith('_service'): - prefix = key[:key.find('_')] + if key.startswith("comments") and key.endswith("_service"): + prefix = key[: key.find("_")] service_s = value node = item["{}{}".format(prefix, "_node")] # time to get the comments - d = self._p.getItems(client, jid.JID(service_s), node, max_comments, rsm_request=rsm_comments, extra=extra_comments) + d = self._p.getItems( + client, + jid.JID(service_s), + node, + max_comments, + rsm_request=rsm_comments, + extra=extra_comments, + ) # then serialise - d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) + d.addCallback( + lambda items_data: self._p.serItemsDataD( + items_data, self.item2mbdata + ) + ) # with failure handling - d.addCallback(lambda serialised_items_data: ('',) + serialised_items_data) + d.addCallback( + lambda serialised_items_data: ("",) + serialised_items_data + ) d.addErrback(lambda failure: (unicode(failure.value), [], {})) # and associate with service/node (needed if there are several comments nodes) - d.addCallback(lambda serialised, service_s=service_s, node=node: (service_s, node) + serialised) + d.addCallback( + lambda serialised, service_s=service_s, node=node: ( + service_s, + node, + ) + + serialised + ) dlist.append(d) # we get the comments comments_d = defer.gatherResults(dlist) # and add them to the item data - comments_d.addCallback(lambda comments_data, item=item: (item, comments_data)) + comments_d.addCallback( + lambda comments_data, item=item: (item, comments_data) + ) items_dlist.append(comments_d) # we gather the items + comments in a list items_d = defer.gatherResults(items_dlist) @@ -951,11 +1271,15 @@ class XEP_0277(object): deferreds = {} for service, node in node_data: - d = deferreds[(service, node)] = self._p.getItems(client, service, node, max_items, rsm_request=rsm_request, extra=extra) - d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) + d = deferreds[(service, node)] = self._p.getItems( + client, service, node, max_items, rsm_request=rsm_request, extra=extra + ) + d.addCallback( + lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata) + ) d.addCallback(getComments) - d.addCallback(lambda items_comments_data: ('', items_comments_data)) - d.addErrback(lambda failure: (unicode(failure.value), ([],{}))) + d.addCallback(lambda items_comments_data: ("", items_comments_data)) + d.addErrback(lambda failure: (unicode(failure.value), ([], {}))) return self.rt_sessions.newSession(deferreds, client.profile) @@ -963,8 +1287,8 @@ class XEP_0277(object): class XEP_0277_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_MICROBLOG)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0280.py b/sat/plugins/plugin_xep_0280.py index 2db08f2d..3abe915f 100755 --- a/sat/plugins/plugin_xep_0280.py +++ b/sat/plugins/plugin_xep_0280.py @@ -19,6 +19,7 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.core.constants import Const as C @@ -27,6 +28,7 @@ from twisted.words.protocols.jabber.error import StanzaError from twisted.internet import defer from wokkel import disco, iwokkel from zope.interface import implements + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -36,7 +38,7 @@ except ImportError: PARAM_CATEGORY = "Misc" PARAM_NAME = "carbon" PARAM_LABEL = D_(u"Message carbons") -NS_CARBONS = 'urn:xmpp:carbons:2' +NS_CARBONS = "urn:xmpp:carbons:2" PLUGIN_INFO = { C.PI_NAME: u"XEP-0280 Plugin", @@ -46,12 +48,12 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: [], C.PI_MAIN: u"XEP_0280", C.PI_HANDLER: u"yes", - C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons""") + C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons"""), } class XEP_0280(object): - # TODO: param is only checked at profile connection + # TODO: param is only checked at profile connection # activate carbons on param change even after profile connection # TODO: chat state notifications are not handled yet (and potentially other XEPs?) @@ -64,11 +66,11 @@ class XEP_0280(object): """.format( - category_name = PARAM_CATEGORY, - category_label = D_(PARAM_CATEGORY), - param_name = PARAM_NAME, - param_label = PARAM_LABEL, - ) + category_name=PARAM_CATEGORY, + category_label=D_(PARAM_CATEGORY), + param_name=PARAM_NAME, + param_label=PARAM_LABEL, + ) def __init__(self, host): log.info(_("Plugin XEP_0280 initialization")) @@ -86,15 +88,17 @@ class XEP_0280(object): (in particular end 2 end encryption plugins) @param message_elt(domish.Element): stanza """ - if message_elt.name != u'message': + if message_elt.name != u"message": log.error(u"addPrivateElt must be used with - """ % (Const.JID_STR[1], - Const.JID_STR[0], - "test" if state == "active" else "", - state, plugin.NS_CHAT_STATES) + """ % ( + Const.JID_STR[1], + Const.JID_STR[0], + "test" if state == "active" else "", + state, + plugin.NS_CHAT_STATES, + ) stanza = parseXml(xml.encode("utf-8")) - self.host.bridge.expectCall("chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0]) - self.plugin.messageReceivedTrigger(self.host.getClient(Const.PROFILE[0]), stanza, None) + self.host.bridge.expectCall( + "chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0] + ) + self.plugin.messageReceivedTrigger( + self.host.getClient(Const.PROFILE[0]), stanza, None + ) def test_messageSendTrigger(self): def cb(data): - xml = data['xml'].toXml().encode("utf-8") + xml = data["xml"].toXml().encode("utf-8") self.assertEqualXML(xml, expected.toXml().encode("utf-8")) d_list = [] for state in plugin.CHAT_STATES: - mess_data = {"to": Const.JID[0], - "type": "chat", - "message": "content", - "extra": {} if state == "active" else {"chat_state": state}} + mess_data = { + "to": Const.JID[0], + "type": "chat", + "message": "content", + "extra": {} if state == "active" else {"chat_state": state}, + } stanza = u"""stanzas") return - message_elt.addElement((NS_CARBONS, u'private')) + message_elt.addElement((NS_CARBONS, u"private")) @defer.inlineCallbacks def profileConnected(self, client): """activate message carbons on connection if possible and activated in config""" - activate = self.host.memory.getParamA(PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile) + activate = self.host.memory.getParamA( + PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile + ) if not activate: log.info(_(u"Not activating message carbons as requested in params")) return @@ -105,7 +109,7 @@ class XEP_0280(object): else: log.info(_(u"message carbons available, enabling it")) iq_elt = client.IQ() - iq_elt.addElement((NS_CARBONS, 'enable')) + iq_elt.addElement((NS_CARBONS, "enable")) try: yield iq_elt.send() except StanzaError as e: @@ -126,40 +130,45 @@ class XEP_0280(object): # we continue normal behaviour return True - if message_elt['from'] != client.jid.userhost(): - log.warning(u"The message carbon received is not from our server, hack attempt?\n{xml}".format( - xml = message_elt.toXml(), - )) + if message_elt["from"] != client.jid.userhost(): + log.warning( + u"The message carbon received is not from our server, hack attempt?\n{xml}".format( + xml=message_elt.toXml() + ) + ) return - forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, 'forwarded')) - cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, 'message')) - if carbons_elt.name == 'received': + forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, "forwarded")) + cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, "message")) + if carbons_elt.name == "received": # on receive we replace the wrapping message with the CCed one # and continue the normal behaviour - message_elt['from'] = cc_message_elt['from'] + message_elt["from"] = cc_message_elt["from"] del message_elt.children[:] for c in cc_message_elt.children: message_elt.addChild(c) return True - elif carbons_elt.name == 'sent': + elif carbons_elt.name == "sent": # on send we parse the message and just add it to history # and send it to frontends (without normal sending treatments) mess_data = SatMessageProtocol.parseMessage(cc_message_elt, client) - if not mess_data['message'] and not mess_data['subject']: + if not mess_data["message"] and not mess_data["subject"]: return False client.messageAddToHistory(mess_data) client.messageSendToBridge(mess_data) else: - log.warning(u"invalid message carbons received:\n{xml}".format( - xml = message_elt.toXml())) + log.warning( + u"invalid message carbons received:\n{xml}".format( + xml=message_elt.toXml() + ) + ) return False class XEP_0280_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_CARBONS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0297.py b/sat/plugins/plugin_xep_0297.py index 61089567..f6df3355 100644 --- a/sat/plugins/plugin_xep_0297.py +++ b/sat/plugins/plugin_xep_0297.py @@ -21,9 +21,11 @@ from sat.core.constants import Const as C from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -41,7 +43,7 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: [u"XEP-0297"], C.PI_MAIN: "XEP_0297", C.PI_HANDLER: u"yes", - C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding""") + C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding"""), } @@ -70,7 +72,7 @@ class XEP_0297(object): if isinstance(child, domish.Element) and not child.uri: XEP_0297.updateUri(child, uri) - def forward(self, stanza, to_jid, stamp, body='', profile_key=C.PROF_KEY_NONE): + def forward(self, stanza, to_jid, stamp, body="", profile_key=C.PROF_KEY_NONE): """Forward a message to the given JID. @param stanza (domish.Element): original stanza to be forwarded. @@ -82,27 +84,29 @@ class XEP_0297(object): """ # FIXME: this method is not used and doesn't use mess_data which should be used for client.sendMessageData # should it be deprecated? A method constructing the element without sending it seems more natural - log.warning(u"THIS METHOD IS DEPRECATED") # FIXME: we use this warning until we check the method - msg = domish.Element((None, 'message')) - msg['to'] = to_jid.full() - msg['type'] = stanza['type'] + log.warning( + u"THIS METHOD IS DEPRECATED" + ) # FIXME: we use this warning until we check the method + msg = domish.Element((None, "message")) + msg["to"] = to_jid.full() + msg["type"] = stanza["type"] - body_elt = domish.Element((None, 'body')) + body_elt = domish.Element((None, "body")) if body: body_elt.addContent(body) - forwarded_elt = domish.Element((NS_SF, 'forwarded')) - delay_elt = self.host.plugins['XEP-0203'].delay(stamp) + forwarded_elt = domish.Element((NS_SF, "forwarded")) + delay_elt = self.host.plugins["XEP-0203"].delay(stamp) forwarded_elt.addChild(delay_elt) if not stanza.uri: # None or '' - XEP_0297.updateUri(stanza, 'jabber:client') + XEP_0297.updateUri(stanza, "jabber:client") forwarded_elt.addChild(stanza) msg.addChild(body_elt) msg.addChild(forwarded_elt) client = self.host.getClient(profile_key) - return client.sendMessageData({u'xml': msg}) + return client.sendMessageData({u"xml": msg}) class XEP_0297_handler(XMPPHandler): @@ -113,8 +117,8 @@ class XEP_0297_handler(XMPPHandler): self.host = plugin_parent.host self.profile = profile - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_SF)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0300.py b/sat/plugins/plugin_xep_0300.py index 8abd5770..fc3fd755 100644 --- a/sat/plugins/plugin_xep_0300.py +++ b/sat/plugins/plugin_xep_0300.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from twisted.words.xish import domish @@ -41,27 +42,29 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0300"], C.PI_MAIN: "XEP_0300", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Management of cryptographic hashes""") + C.PI_DESCRIPTION: _("""Management of cryptographic hashes"""), } NS_HASHES = "urn:xmpp:hashes:2" NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}" -BUFFER_SIZE = 2**12 -ALGO_DEFAULT = 'sha-256' +BUFFER_SIZE = 2 ** 12 +ALGO_DEFAULT = "sha-256" class XEP_0300(object): # TODO: add blake after moving to Python 3 - ALGOS = OrderedDict(( - (u'md5', hashlib.md5), - (u'sha-1', hashlib.sha1), - (u'sha-256', hashlib.sha256), - (u'sha-512', hashlib.sha512), - )) + ALGOS = OrderedDict( + ( + (u"md5", hashlib.md5), + (u"sha-1", hashlib.sha1), + (u"sha-256", hashlib.sha256), + (u"sha-512", hashlib.sha512), + ) + ) def __init__(self, host): log.info(_("plugin Hashes initialization")) - host.registerNamespace('hashes', NS_HASHES) + host.registerNamespace("hashes", NS_HASHES) def getHandler(self, client): return XEP_0300_handler() @@ -90,11 +93,15 @@ class XEP_0300(object): """ client = self.host.getClient(profile) for algo in reversed(XEP_0300.ALGOS): - has_feature = yield self.host.hasFeature(client, NS_HASHES_FUNCTIONS.format(algo), to_jid) + has_feature = yield self.host.hasFeature( + client, NS_HASHES_FUNCTIONS.format(algo), to_jid + ) if has_feature: - log.debug(u"Best hashing algorithm found for {jid}: {algo}".format( - jid=to_jid.full(), - algo=algo)) + log.debug( + u"Best hashing algorithm found for {jid}: {algo}".format( + jid=to_jid.full(), algo=algo + ) + ) defer.returnValue(algo) def _calculateHashBlocking(self, file_obj, hasher): @@ -123,16 +130,18 @@ class XEP_0300(object): @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS @return (D(domish.Element)): hash element """ + def hashCalculated(hash_): return self.buildHashElt(hash_, algo) + hasher = self.ALGOS[algo] hash_d = self.calculateHash(file_obj, hasher) hash_d.addCallback(hashCalculated) return hash_d def buildHashUsedElt(self, algo=ALGO_DEFAULT): - hash_used_elt = domish.Element((NS_HASHES, 'hash-used')) - hash_used_elt['algo'] = algo + hash_used_elt = domish.Element((NS_HASHES, "hash-used")) + hash_used_elt["algo"] = algo return hash_used_elt def parseHashUsedElt(self, parent): @@ -144,10 +153,10 @@ class XEP_0300(object): @raise exceptions.DataError: the element is invalid """ try: - hash_used_elt = next(parent.elements(NS_HASHES, 'hash-used')) + hash_used_elt = next(parent.elements(NS_HASHES, "hash-used")) except StopIteration: raise exceptions.NotFound - algo = hash_used_elt[u'algo'] + algo = hash_used_elt[u"algo"] if not algo: raise exceptions.DataError return algo @@ -161,10 +170,10 @@ class XEP_0300(object): """ assert hash_ assert algo - hash_elt = domish.Element((NS_HASHES, 'hash')) + hash_elt = domish.Element((NS_HASHES, "hash")) if hash_ is not None: hash_elt.addContent(base64.b64encode(hash_)) - hash_elt['algo'] = algo + hash_elt["algo"] = algo return hash_elt def parseHashElt(self, parent): @@ -181,8 +190,8 @@ class XEP_0300(object): hash_elt = None best_algo = None best_value = None - for hash_elt in parent.elements(NS_HASHES, 'hash'): - algo = hash_elt.getAttribute('algo') + for hash_elt in parent.elements(NS_HASHES, "hash"): + algo = hash_elt.getAttribute("algo") try: idx = algos.index(algo) except ValueError: @@ -204,9 +213,12 @@ class XEP_0300(object): class XEP_0300_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): - hash_functions_names = [disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) for algo in XEP_0300.ALGOS] + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): + hash_functions_names = [ + disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) + for algo in XEP_0300.ALGOS + ] return [disco.DiscoFeature(NS_HASHES)] + hash_functions_names - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0313.py b/sat/plugins/plugin_xep_0313.py index f8be8389..5a0fc9d6 100644 --- a/sat/plugins/plugin_xep_0313.py +++ b/sat/plugins/plugin_xep_0313.py @@ -21,6 +21,7 @@ from sat.core.constants import Const as C from sat.core.i18n import _ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions @@ -45,12 +46,11 @@ PLUGIN_INFO = { C.PI_PROTOCOLS: ["XEP-0313"], C.PI_MAIN: "XEP_0313", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of Message Archive Management""") + C.PI_DESCRIPTION: _("""Implementation of Message Archive Management"""), } class XEP_0313(object): - def __init__(self, host): log.info(_("Message Archive Management plugin initialization")) self.host = host @@ -86,7 +86,7 @@ class XEP_0313(object): def _queryFinished(self, iq_result, client, elt_list, event): client.xmlstream.removeObserver(event, self._appendMessage) try: - fin_elt = iq_result.elements(mam.NS_MAM, 'fin').next() + fin_elt = iq_result.elements(mam.NS_MAM, "fin").next() except StopIteration: raise exceptions.DataError(u"Invalid MAM result") @@ -119,14 +119,21 @@ class XEP_0313(object): # http://xmpp.org/extensions/xep-0313.html#prefs return client._mam.queryPrefs(service) - def _setPrefs(self, service_s=None, default='roster', always=None, never=None, profile_key=C.PROF_KEY_NONE): + def _setPrefs( + self, + service_s=None, + default="roster", + always=None, + never=None, + profile_key=C.PROF_KEY_NONE, + ): service = jid.JID(service_s) if service_s else None always_jid = [jid.JID(entity) for entity in always] never_jid = [jid.JID(entity) for entity in never] - #TODO: why not build here a MAMPrefs object instead of passing the args separately? + # TODO: why not build here a MAMPrefs object instead of passing the args separately? return self.setPrefs(service, default, always_jid, never_jid, profile_key) - def setPrefs(self, client, service=None, default='roster', always=None, never=None): + def setPrefs(self, client, service=None, default="roster", always=None, never=None): """Set news user preferences. @param service: entity offering the MAM service (None for user archives) @@ -143,8 +150,8 @@ class XEP_0313(object): class SatMAMClient(mam.MAMClient): implements(disco.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(mam.NS_MAM)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0329.py b/sat/plugins/plugin_xep_0329.py index db512431..f833d22d 100644 --- a/sat/plugins/plugin_xep_0329.py +++ b/sat/plugins/plugin_xep_0329.py @@ -21,6 +21,7 @@ from sat.core.i18n import _ from sat.core import exceptions from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.tools import stream from sat.tools.common import regex @@ -44,30 +45,35 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300"], C.PI_MAIN: "XEP_0329", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing""") + C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing"""), } -NS_FIS = 'urn:xmpp:fis:0' +NS_FIS = "urn:xmpp:fis:0" IQ_FIS_REQUEST = C.IQ_GET + '/query[@xmlns="' + NS_FIS + '"]' SINGLE_FILES_DIR = u"files" -TYPE_VIRTUAL= u'virtual' -TYPE_PATH = u'path' -SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) -KEY_TYPE = u'type' +TYPE_VIRTUAL = u"virtual" +TYPE_PATH = u"path" +SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL) +KEY_TYPE = u"type" class ShareNode(object): """node containing directory or files to share, virtual or real""" + host = None def __init__(self, name, parent, type_, access, path=None): assert type_ in SHARE_TYPES if name is not None: - if name == u'..' or u'/' in name or u'\\' in name: - log.warning(_(u'path change chars found in name [{name}], hack attempt?').format(name=name)) - if name == u'..': - name = u'--' + if name == u".." or u"/" in name or u"\\" in name: + log.warning( + _(u"path change chars found in name [{name}], hack attempt?").format( + name=name + ) + ) + if name == u"..": + name = u"--" else: name = regex.pathEscape(name) self.name = name @@ -125,7 +131,9 @@ class ShareNode(object): try: del self.parent.children[self.name] except TypeError: - raise exceptions.InternalError(u'trying to remove a node from inexisting parent') + raise exceptions.InternalError( + u"trying to remove a node from inexisting parent" + ) except KeyError: raise exceptions.InternalError(u"node not found in parent's children") self.parent = None @@ -138,7 +146,7 @@ class ShareNode(object): @param peer_jid(jid.JID): entity which try to access the node @return (bool): True if entity can access """ - file_data = {u'access':self.access, u'owner': client.jid.userhostJID()} + file_data = {u"access": self.access, u"owner": client.jid.userhostJID()} try: self.host.memory.checkFilePermission(file_data, peer_jid, perms) except exceptions.PermissionError: @@ -146,7 +154,9 @@ class ShareNode(object): else: return True - def checkPermissions(self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True): + def checkPermissions( + self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True + ): """check that peer_jid can access this node and all its parents @param peer_jid(jid.JID): entrity trying to access the node @@ -178,15 +188,18 @@ class ShareNode(object): @raise exceptions.DataError: path is invalid @raise NotFound: path lead to a non existing file/directory """ - path_elts = filter(None, path.split(u'/')) + path_elts = filter(None, path.split(u"/")) - if u'..' in path_elts: - log.warning(_(u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]').format( - path=path, profile=client.profile)) + if u".." in path_elts: + log.warning( + _( + u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]' + ).format(path=path, profile=client.profile) + ) raise exceptions.PermissionError(u"illegal path elements") if not path_elts: - raise exceptions.DataError(_(u'path is invalid: {path}').format(path=path)) + raise exceptions.DataError(_(u"path is invalid: {path}").format(path=path)) node = client._XEP_0329_root_node @@ -199,10 +212,10 @@ class ShareNode(object): elif node.type == TYPE_PATH: break - if not node.checkPermissions(client, peer_jid, perms = perms): + if not node.checkPermissions(client, peer_jid, perms=perms): raise exceptions.PermissionError(u"permission denied") - return node, u'/'.join(path_elts) + return node, u"/".join(path_elts) def findByLocalPath(self, path): """retrieve nodes linking to local path @@ -223,8 +236,9 @@ class ShareNode(object): elif node.type == TYPE_PATH: paths.setdefault(node.path, []).append(node) else: - raise exceptions.InternalError(u'unknown node type: {type}'.format( - type = node.type)) + raise exceptions.InternalError( + u"unknown node type: {type}".format(type=node.type) + ) def getSharedPaths(self): """retrieve nodes by shared path @@ -233,38 +247,71 @@ class ShareNode(object): @return (dict): map from shared path to list of nodes """ if self.type == TYPE_PATH: - raise exceptions.InternalError("getSharedPaths must be used on a virtual node") + raise exceptions.InternalError( + "getSharedPaths must be used on a virtual node" + ) paths = {} self._getSharedPaths(self, paths) return paths class XEP_0329(object): - def __init__(self, host): log.info(_("File Information Sharing initialization")) self.host = host ShareNode.host = host - self._h = host.plugins['XEP-0300'] - self._jf = host.plugins['XEP-0234'] - host.bridge.addMethod("FISList", ".plugin", in_sign='ssa{ss}s', out_sign='aa{ss}', method=self._listFiles, async=True) - host.bridge.addMethod("FISLocalSharesGet", ".plugin", in_sign='s', out_sign='as', method=self._localSharesGet) - host.bridge.addMethod("FISSharePath", ".plugin", in_sign='ssss', out_sign='s', method=self._sharePath) - host.bridge.addMethod("FISUnsharePath", ".plugin", in_sign='ss', out_sign='', method=self._unsharePath) - host.bridge.addSignal("FISSharedPathNew", ".plugin", signature='sss') - host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature='ss') + self._h = host.plugins["XEP-0300"] + self._jf = host.plugins["XEP-0234"] + host.bridge.addMethod( + "FISList", + ".plugin", + in_sign="ssa{ss}s", + out_sign="aa{ss}", + method=self._listFiles, + async=True, + ) + host.bridge.addMethod( + "FISLocalSharesGet", + ".plugin", + in_sign="s", + out_sign="as", + method=self._localSharesGet, + ) + host.bridge.addMethod( + "FISSharePath", + ".plugin", + in_sign="ssss", + out_sign="s", + method=self._sharePath, + ) + host.bridge.addMethod( + "FISUnsharePath", + ".plugin", + in_sign="ss", + out_sign="", + method=self._unsharePath, + ) + host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss") + host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss") host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger) - host.registerNamespace('fis', NS_FIS) + host.registerNamespace("fis", NS_FIS) def getHandler(self, client): return XEP_0329_handler(self) def profileConnected(self, client): if not client.is_component: - client._XEP_0329_root_node = ShareNode(None, None, TYPE_VIRTUAL, {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}) - client._XEP_0329_names_data = {} # name to share map + client._XEP_0329_root_node = ShareNode( + None, + None, + TYPE_VIRTUAL, + {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}, + ) + client._XEP_0329_names_data = {} # name to share map - def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt): + def _fileSendingRequestTrigger( + self, client, session, content_data, content_name, file_data, file_elt + ): """this trigger check that a requested file is available, and fill suitable data if so path and name are used to retrieve the file. If path is missing, we try our luck with known names @@ -273,21 +320,21 @@ class XEP_0329(object): return True, None try: - name = file_data[u'name'] + name = file_data[u"name"] except KeyError: return True, None - assert u'/' not in name + assert u"/" not in name - path = file_data.get(u'path') + path = file_data.get(u"path") if path is not None: # we have a path, we can follow it to find node try: - node, rem_path = ShareNode.find(client, path, session[u'peer_jid']) + node, rem_path = ShareNode.find(client, path, session[u"peer_jid"]) except (exceptions.PermissionError, exceptions.NotFound): - # no file, or file not allowed, we continue normal workflow + # no file, or file not allowed, we continue normal workflow return True, None except exceptions.DataError: - log.warning(_(u'invalid path: {path}').format(path=path)) + log.warning(_(u"invalid path: {path}").format(path=path)) return True, None if node.type == TYPE_VIRTUAL: @@ -300,7 +347,9 @@ class XEP_0329(object): # we have a path node, so we can retrieve the full path now path = os.path.join(node.path, rem_path, name) else: - raise exceptions.InternalError(u'unknown type: {type}'.format(type=node.type)) + raise exceptions.InternalError( + u"unknown type: {type}".format(type=node.type) + ) if not os.path.exists(path): return True, None size = os.path.getsize(path) @@ -312,49 +361,56 @@ class XEP_0329(object): return True, None for path, shared_file in name_data.iteritems(): - if True: # FIXME: filters are here + if True: # FIXME: filters are here break else: return True, None - parent_node = shared_file[u'parent'] - if not parent_node.checkPermissions(client, session[u'peer_jid']): - log.warning(_(u"{peer_jid} requested a file (s)he can't access [{profile}]").format( - peer_jid = session[u'peer_jid'], profile = client.profile)) + parent_node = shared_file[u"parent"] + if not parent_node.checkPermissions(client, session[u"peer_jid"]): + log.warning( + _( + u"{peer_jid} requested a file (s)he can't access [{profile}]" + ).format(peer_jid=session[u"peer_jid"], profile=client.profile) + ) return True, None - size = shared_file[u'size'] + size = shared_file[u"size"] - file_data[u'size'] = size - file_elt.addElement(u'size', content=unicode(size)) - hash_algo = file_data[u'hash_algo'] = self._h.getDefaultAlgo() - hasher = file_data[u'hash_hasher'] = self._h.getHasher(hash_algo) + file_data[u"size"] = size + file_elt.addElement(u"size", content=unicode(size)) + hash_algo = file_data[u"hash_algo"] = self._h.getDefaultAlgo() + hasher = file_data[u"hash_hasher"] = self._h.getHasher(hash_algo) file_elt.addChild(self._h.buildHashUsedElt(hash_algo)) - content_data['stream_object'] = stream.FileStreamObject( + content_data["stream_object"] = stream.FileStreamObject( self.host, client, path, uid=self._jf.getProgressId(session, content_name), size=size, data_cb=lambda data: hasher.update(data), - ) + ) return False, True # common methods def _requestHandler(self, client, iq_elt, root_nodes_cb, files_from_node_cb): iq_elt.handled = True - owner = jid.JID(iq_elt['from']).userhostJID() - node = iq_elt.query.getAttribute('node') + owner = jid.JID(iq_elt["from"]).userhostJID() + node = iq_elt.query.getAttribute("node") if not node: d = defer.maybeDeferred(root_nodes_cb, client, iq_elt, owner) else: d = defer.maybeDeferred(files_from_node_cb, client, iq_elt, owner, node) - d.addErrback(lambda failure_: log.error(_(u"error while retrieving files: {msg}").format(msg=failure_))) + d.addErrback( + lambda failure_: log.error( + _(u"error while retrieving files: {msg}").format(msg=failure_) + ) + ) def _iqError(self, client, iq_elt, condition="item-not-found"): error_elt = jabber_error.StanzaError(condition).toResponse(iq_elt) client.send(error_elt) - # client + # client def _addPathData(self, client, query_elt, path, parent_node): """Fill query_elt with files/directories found in path""" @@ -362,23 +418,24 @@ class XEP_0329(object): if os.path.isfile(path): size = os.path.getsize(path) mime_type = mimetypes.guess_type(path, strict=False)[0] - file_elt = self._jf.buildFileElement(name = name, - size = size, - mime_type = mime_type, - modified = os.path.getmtime(path)) + file_elt = self._jf.buildFileElement( + name=name, size=size, mime_type=mime_type, modified=os.path.getmtime(path) + ) query_elt.addChild(file_elt) # we don't specify hash as it would be too resource intensive to calculate it for all files # we add file to name_data, so users can request it later name_data = client._XEP_0329_names_data.setdefault(name, {}) if path not in name_data: - name_data[path] = {'size': size, - 'mime_type': mime_type, - 'parent': parent_node} + name_data[path] = { + "size": size, + "mime_type": mime_type, + "parent": parent_node, + } else: # we have a directory - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name def _pathNodeHandler(self, client, iq_elt, query_elt, node, path): """Fill query_elt for path nodes, i.e. physical directories""" @@ -390,13 +447,15 @@ class XEP_0329(object): elif os.path.isfile(path): self._addPathData(client, query_elt, path, node) else: - for name in sorted(os.listdir(path.encode('utf-8')), key=lambda n: n.lower()): + for name in sorted(os.listdir(path.encode("utf-8")), key=lambda n: n.lower()): try: - name = name.decode('utf-8', 'strict') + name = name.decode("utf-8", "strict") except UnicodeDecodeError as e: - log.warning(_(u"ignoring invalid unicode name ({name}): {msg}").format( - name = name.decode('utf-8', 'replace'), - msg = e)) + log.warning( + _(u"ignoring invalid unicode name ({name}): {msg}").format( + name=name.decode("utf-8", "replace"), msg=e + ) + ) continue full_path = os.path.join(path, name) self._addPathData(client, query_elt, full_path, node) @@ -408,68 +467,79 @@ class XEP_0329(object): continue node_type = child_node.type if node_type == TYPE_VIRTUAL: - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name elif node_type == TYPE_PATH: self._addPathData(client, query_elt, child_node.path, child_node) else: - raise exceptions.InternalError(_(u'unexpected type: {type}').format(type=node_type)) + raise exceptions.InternalError( + _(u"unexpected type: {type}").format(type=node_type) + ) def _getRootNodesCb(self, client, iq_elt, owner): - peer_jid = jid.JID(iq_elt['from']) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) + peer_jid = jid.JID(iq_elt["from"]) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) for name, node in client._XEP_0329_root_node.iteritems(): if not node.checkPermissions(client, peer_jid, check_parents=False): continue - directory_elt = query_elt.addElement('directory') - directory_elt['name'] = name + directory_elt = query_elt.addElement("directory") + directory_elt["name"] = name client.send(iq_result_elt) def _getFilesFromNodeCb(self, client, iq_elt, owner, node_path): """Main method to retrieve files/directories from a node_path""" - peer_jid = jid.JID(iq_elt[u'from']) + peer_jid = jid.JID(iq_elt[u"from"]) try: node, path = ShareNode.find(client, node_path, peer_jid) except (exceptions.PermissionError, exceptions.NotFound): return self._iqError(client, iq_elt) except exceptions.DataError: - return self._iqError(client, iq_elt, condition='not-acceptable') + return self._iqError(client, iq_elt, condition="not-acceptable") node_type = node.type - peer_jid = jid.JID(iq_elt['from']) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) - query_elt[u'node'] = node_path + peer_jid = jid.JID(iq_elt["from"]) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) + query_elt[u"node"] = node_path # we now fill query_elt according to node_type if node_type == TYPE_PATH: - # it's a physical path + # it's a physical path self._pathNodeHandler(client, iq_elt, query_elt, node, path) elif node_type == TYPE_VIRTUAL: assert not path self._virtualNodeHandler(client, peer_jid, iq_elt, query_elt, node) else: - raise exceptions.InternalError(_(u'unknown node type: {type}').format(type=node_type)) + raise exceptions.InternalError( + _(u"unknown node type: {type}").format(type=node_type) + ) client.send(iq_result_elt) def onRequest(self, iq_elt, client): - return self._requestHandler(client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb) + return self._requestHandler( + client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb + ) # Component @defer.inlineCallbacks def _compGetRootNodesCb(self, client, iq_elt, owner): - peer_jid = jid.JID(iq_elt['from']) - files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, parent=u'', - type_=C.FILE_TYPE_DIRECTORY, owner=owner) - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) + peer_jid = jid.JID(iq_elt["from"]) + files_data = yield self.host.memory.getFiles( + client, + peer_jid=peer_jid, + parent=u"", + type_=C.FILE_TYPE_DIRECTORY, + owner=owner, + ) + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) for file_data in files_data: - name = file_data[u'name'] - directory_elt = query_elt.addElement(u'directory') - directory_elt[u'name'] = name + name = file_data[u"name"] + directory_elt = query_elt.addElement(u"directory") + directory_elt[u"name"] = name client.send(iq_result_elt) @defer.inlineCallbacks @@ -479,44 +549,53 @@ class XEP_0329(object): result stanza is then built and sent to requestor @trigger XEP-0329_compGetFilesFromNode(client, iq_elt, owner, node_path, files_data): can be used to add data/elements """ - peer_jid = jid.JID(iq_elt['from']) + peer_jid = jid.JID(iq_elt["from"]) try: - files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, path=node_path, owner=owner) + files_data = yield self.host.memory.getFiles( + client, peer_jid=peer_jid, path=node_path, owner=owner + ) except exceptions.NotFound: self._iqError(client, iq_elt) return - iq_result_elt = xmlstream.toResponse(iq_elt, 'result') - query_elt = iq_result_elt.addElement((NS_FIS, 'query')) - query_elt[u'node'] = node_path - if not self.host.trigger.point(u'XEP-0329_compGetFilesFromNode', client, iq_elt, owner, node_path, files_data): + iq_result_elt = xmlstream.toResponse(iq_elt, "result") + query_elt = iq_result_elt.addElement((NS_FIS, "query")) + query_elt[u"node"] = node_path + if not self.host.trigger.point( + u"XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data + ): return for file_data in files_data: - file_elt = self._jf.buildFileElementFromDict(file_data, - modified=file_data.get(u'modified', file_data[u'created'])) + file_elt = self._jf.buildFileElementFromDict( + file_data, modified=file_data.get(u"modified", file_data[u"created"]) + ) query_elt.addChild(file_elt) client.send(iq_result_elt) def onComponentRequest(self, iq_elt, client): - return self._requestHandler(client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb) + return self._requestHandler( + client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb + ) def _parseResult(self, iq_elt): - query_elt = next(iq_elt.elements(NS_FIS, 'query')) + query_elt = next(iq_elt.elements(NS_FIS, "query")) files = [] for elt in query_elt.elements(): - if elt.name == 'file': + if elt.name == "file": # we have a file try: file_data = self._jf.parseFileElement(elt) except exceptions.DataError: continue - file_data[u'type'] = C.FILE_TYPE_FILE - elif elt.name == 'directory' and elt.uri == NS_FIS: + file_data[u"type"] = C.FILE_TYPE_FILE + elif elt.name == "directory" and elt.uri == NS_FIS: # we have a directory - file_data = {'name': elt['name'], 'type': C.FILE_TYPE_DIRECTORY} + file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY} else: - log.warning(_(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml())) + log.warning( + _(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml()) + ) continue files.append(file_data) return files @@ -526,7 +605,9 @@ class XEP_0329(object): def _serializeData(self, files_data): for file_data in files_data: for key, value in file_data.iteritems(): - file_data[key] = json.dumps(value) if key in ('extra',) else unicode(value) + file_data[key] = ( + json.dumps(value) if key in ("extra",) else unicode(value) + ) return files_data def _listFiles(self, target_jid, path, extra, profile): @@ -545,11 +626,11 @@ class XEP_0329(object): @param extra(dict, None): extra data @return list(dict): shared files """ - iq_elt = client.IQ('get') - iq_elt['to'] = target_jid.full() - query_elt = iq_elt.addElement((NS_FIS, 'query')) + iq_elt = client.IQ("get") + iq_elt["to"] = target_jid.full() + query_elt = iq_elt.addElement((NS_FIS, "query")) if path: - query_elt['node'] = path + query_elt["node"] = path d = iq_elt.send() d.addCallback(self._parseResult) return d @@ -563,7 +644,7 @@ class XEP_0329(object): def _sharePath(self, name, path, access, profile): client = self.host.getClient(profile) - access= json.loads(access) + access = json.loads(access) return self.sharePath(client, name or None, path, access) def sharePath(self, client, name, path, access): @@ -571,31 +652,36 @@ class XEP_0329(object): raise exceptions.ClientTypeError if not os.path.exists(path): raise ValueError(_(u"This path doesn't exist!")) - if not path or not path.strip(u' /'): + if not path or not path.strip(u" /"): raise ValueError(_(u"A path need to be specified")) if not isinstance(access, dict): - raise ValueError(_(u'access must be a dict')) + raise ValueError(_(u"access must be a dict")) node = client._XEP_0329_root_node node_type = TYPE_PATH if os.path.isfile(path): - # we have a single file, the workflow is diferrent as we store all single files in the same dir + # we have a single file, the workflow is diferrent as we store all single files in the same dir node = node.getOrCreate(SINGLE_FILES_DIR) if not name: - name = os.path.basename(path.rstrip(u' /')) + name = os.path.basename(path.rstrip(u" /")) if not name: raise exceptions.InternalError(_(u"Can't find a proper name")) if name in node or name == SINGLE_FILES_DIR: idx = 1 - new_name = name + '_' + unicode(idx) + new_name = name + "_" + unicode(idx) while new_name in node: idx += 1 - new_name = name + '_' + unicode(idx) + new_name = name + "_" + unicode(idx) name = new_name - log.info(_(u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format( - new_name=new_name, profile=client.profile))) + log.info( + _( + u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format( + new_name=new_name, profile=client.profile + ) + ) + ) ShareNode(name=name, parent=node, type_=node_type, access=access, path=path) self.host.bridge.FISSharedPathNew(path, name, client.profile) @@ -621,12 +707,16 @@ class XEP_0329_handler(xmlstream.XMPPHandler): def connectionInitialized(self): if self.parent.is_component: - self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent + ) else: - self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent) + self.xmlstream.addObserver( + IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent + ) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_FIS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0334.py b/sat/plugins/plugin_xep_0334.py index 7bd275c5..2081540e 100644 --- a/sat/plugins/plugin_xep_0334.py +++ b/sat/plugins/plugin_xep_0334.py @@ -20,12 +20,14 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger + log = getLogger(__name__) from sat.core.constants import Const as C from sat.tools.common import data_format from wokkel import disco, iwokkel + try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: @@ -42,21 +44,24 @@ PLUGIN_INFO = { C.PI_MAIN: "XEP_0334", C.PI_HANDLER: u"yes", C.PI_DESCRIPTION: D_(u"""Implementation of Message Processing Hints"""), - C.PI_USAGE: dedent(D_(u"""\ + C.PI_USAGE: dedent( + D_( + u"""\ Frontends can use HINT_* constants in mess_data['extra'] in a serialized 'hints' dict. Internal plugins can use directly addHint([HINT_* constant]). - Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history.""")) - + Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history.""" + ) + ), } -NS_HINTS = u'urn:xmpp:hints' +NS_HINTS = u"urn:xmpp:hints" class XEP_0334(object): - HINT_NO_PERMANENT_STORE = u'no-permanent-store' - HINT_NO_STORE = u'no-store' - HINT_NO_COPY = u'no-copy' - HINT_STORE = u'store' + HINT_NO_PERMANENT_STORE = u"no-permanent-store" + HINT_NO_STORE = u"no-store" + HINT_NO_COPY = u"no-copy" + HINT_STORE = u"store" HINTS = (HINT_NO_PERMANENT_STORE, HINT_NO_STORE, HINT_NO_COPY, HINT_STORE) def __init__(self, host): @@ -69,38 +74,45 @@ class XEP_0334(object): return XEP_0334_handler() def addHint(self, mess_data, hint): - if hint == self.HINT_NO_COPY and not mess_data['to'].resource: - log.error(u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint)) + if hint == self.HINT_NO_COPY and not mess_data["to"].resource: + log.error( + u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint) + ) return - hints = mess_data.setdefault('hints', set()) + hints = mess_data.setdefault("hints", set()) if hint in self.HINTS: hints.add(hint) else: log.error(u"Unknown hint: {}".format(hint)) def _sendPostXmlTreatment(self, mess_data): - if 'hints' in mess_data: - for hint in mess_data['hints']: - mess_data[u'xml'].addElement((NS_HINTS, hint)) + if "hints" in mess_data: + for hint in mess_data["hints"]: + mess_data[u"xml"].addElement((NS_HINTS, hint)) return mess_data - def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): + def sendMessageTrigger( + self, client, mess_data, pre_xml_treatments, post_xml_treatments + ): """Add the hints element to the message to be sent""" - if u'hints' in mess_data[u'extra']: - for hint in data_format.dict2iter(u'hints', mess_data[u'extra'], pop=True): + if u"hints" in mess_data[u"extra"]: + for hint in data_format.dict2iter(u"hints", mess_data[u"extra"], pop=True): self.addHint(hint) post_xml_treatments.addCallback(self._sendPostXmlTreatment) return True def _receivedSkipHistory(self, mess_data): - mess_data[u'history'] = C.HISTORY_SKIP + mess_data[u"history"] = C.HISTORY_SKIP return mess_data def messageReceivedTrigger(self, client, message_elt, post_treat): """Check for hints in the received message""" for elt in message_elt.elements(): - if elt.uri == NS_HINTS and elt.name in (self.HINT_NO_PERMANENT_STORE, self.HINT_NO_STORE): + if elt.uri == NS_HINTS and elt.name in ( + self.HINT_NO_PERMANENT_STORE, + self.HINT_NO_STORE, + ): log.debug(u"history will be skipped for this message, as requested") post_treat.addCallback(self._receivedSkipHistory) break @@ -110,8 +122,8 @@ class XEP_0334(object): class XEP_0334_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HINTS)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/plugins/plugin_xep_0363.py b/sat/plugins/plugin_xep_0363.py index b77c62b5..5abad5ec 100644 --- a/sat/plugins/plugin_xep_0363.py +++ b/sat/plugins/plugin_xep_0363.py @@ -20,6 +20,7 @@ from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from wokkel import disco, iwokkel @@ -49,18 +50,17 @@ PLUGIN_INFO = { C.PI_DEPENDENCIES: ["FILE", "UPLOAD"], C.PI_MAIN: "XEP_0363", C.PI_HANDLER: "yes", - C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload""") + C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""), } -NS_HTTP_UPLOAD = 'urn:xmpp:http:upload' +NS_HTTP_UPLOAD = "urn:xmpp:http:upload" -Slot = namedtuple('Slot', ['put', 'get']) +Slot = namedtuple("Slot", ["put", "get"]) @implementer(IOpenSSLClientConnectionCreator) class NoCheckConnectionCreator(object): - def __init__(self, hostname, ctx): self._ctx = ctx @@ -80,53 +80,91 @@ class NoCheckContextFactory(ssl.ClientContextFactory): """ def creatorForNetloc(self, hostname, port): - log.warning(u"TLS check disabled for {host} on port {port}".format(host=hostname, port=port)) + log.warning( + u"TLS check disabled for {host} on port {port}".format( + host=hostname, port=port + ) + ) certificateOptions = ssl.CertificateOptions(trustRoot=None) return NoCheckConnectionCreator(hostname, certificateOptions.getContext()) class XEP_0363(object): - def __init__(self, host): log.info(_("plugin HTTP File Upload initialization")) self.host = host - host.bridge.addMethod("fileHTTPUpload", ".plugin", in_sign='sssbs', out_sign='', method=self._fileHTTPUpload) - host.bridge.addMethod("fileHTTPUploadGetSlot", ".plugin", in_sign='sisss', out_sign='(ss)', method=self._getSlot, async=True) - host.plugins['UPLOAD'].register(u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload) + host.bridge.addMethod( + "fileHTTPUpload", + ".plugin", + in_sign="sssbs", + out_sign="", + method=self._fileHTTPUpload, + ) + host.bridge.addMethod( + "fileHTTPUploadGetSlot", + ".plugin", + in_sign="sisss", + out_sign="(ss)", + method=self._getSlot, + async=True, + ) + host.plugins["UPLOAD"].register( + u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload + ) def getHandler(self, client): return XEP_0363_handler() @defer.inlineCallbacks def getHTTPUploadEntity(self, upload_jid=None, profile=C.PROF_KEY_NONE): - """Get HTTP upload capable entity + """Get HTTP upload capable entity upload_jid is checked, then its components @param upload_jid(None, jid.JID): entity to check @return(D(jid.JID)): first HTTP upload capable entity @raise exceptions.NotFound: no entity found """ - client = self.host.getClient(profile) - try: - entity = client.http_upload_service - except AttributeError: - found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) - try: - entity = client.http_upload_service = iter(found_entities).next() - except StopIteration: - entity = client.http_upload_service = None + client = self.host.getClient(profile) + try: + entity = client.http_upload_service + except AttributeError: + found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) + try: + entity = client.http_upload_service = iter(found_entities).next() + except StopIteration: + entity = client.http_upload_service = None - if entity is None: - raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) + if entity is None: + raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found")) - defer.returnValue(entity) + defer.returnValue(entity) - def _fileHTTPUpload(self, filepath, filename='', upload_jid='', ignore_tls_errors=False, profile=C.PROF_KEY_NONE): + def _fileHTTPUpload( + self, + filepath, + filename="", + upload_jid="", + ignore_tls_errors=False, + profile=C.PROF_KEY_NONE, + ): assert os.path.isabs(filepath) and os.path.isfile(filepath) - progress_id_d, dummy = self.fileHTTPUpload(filepath, filename or None, jid.JID(upload_jid) if upload_jid else None, {'ignore_tls_errors': ignore_tls_errors}, profile) + progress_id_d, dummy = self.fileHTTPUpload( + filepath, + filename or None, + jid.JID(upload_jid) if upload_jid else None, + {"ignore_tls_errors": ignore_tls_errors}, + profile, + ) return progress_id_d - def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None, profile=C.PROF_KEY_NONE): + def fileHTTPUpload( + self, + filepath, + filename=None, + upload_jid=None, + options=None, + profile=C.PROF_KEY_NONE, + ): """upload a file through HTTP @param filepath(str): absolute path of the file @@ -141,14 +179,20 @@ class XEP_0363(object): """ if options is None: options = {} - ignore_tls_errors = options.get('ignore_tls_errors', False) + ignore_tls_errors = options.get("ignore_tls_errors", False) client = self.host.getClient(profile) filename = filename or os.path.basename(filepath) size = os.path.getsize(filepath) progress_id_d = defer.Deferred() download_d = defer.Deferred() d = self.getSlot(client, filename, size, upload_jid=upload_jid) - d.addCallbacks(self._getSlotCb, self._getSlotEb, (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), None, (client, progress_id_d, download_d)) + d.addCallbacks( + self._getSlotCb, + self._getSlotEb, + (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), + None, + (client, progress_id_d, download_d), + ) return progress_id_d, download_d def _getSlotEb(self, fail, client, progress_id_d, download_d): @@ -157,7 +201,9 @@ class XEP_0363(object): progress_id_d.errback(fail) download_d.errback(fail) - def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False): + def _getSlotCb( + self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False + ): """Called when slot is received, try to do the upload @param slot(Slot): slot instance with the get and put urls @@ -169,15 +215,28 @@ class XEP_0363(object): @return (tuple """ log.debug(u"Got upload slot: {}".format(slot)) - sat_file = self.host.plugins['FILE'].File(self.host, client, path, size=size, auto_end_signals=False) + sat_file = self.host.plugins["FILE"].File( + self.host, client, path, size=size, auto_end_signals=False + ) progress_id_d.callback(sat_file.uid) file_producer = http_client.FileBodyProducer(sat_file) if ignore_tls_errors: agent = http_client.Agent(reactor, NoCheckContextFactory()) else: agent = http_client.Agent(reactor) - d = agent.request('PUT', slot.put.encode('utf-8'), http_headers.Headers({'User-Agent': [C.APP_NAME.encode('utf-8')]}), file_producer) - d.addCallbacks(self._uploadCb, self._uploadEb, (sat_file, slot, download_d), None, (sat_file, download_d)) + d = agent.request( + "PUT", + slot.put.encode("utf-8"), + http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}), + file_producer, + ) + d.addCallbacks( + self._uploadCb, + self._uploadEb, + (sat_file, slot, download_d), + None, + (sat_file, download_d), + ) return d def _uploadCb(self, dummy, sat_file, slot, download_d): @@ -188,7 +247,7 @@ class XEP_0363(object): @param slot(Slot): put/get urls """ log.info(u"HTTP upload finished") - sat_file.progressFinished({'url': slot.get}) + sat_file.progressFinished({"url": slot.get}) download_d.callback(slot.get) def _uploadEb(self, fail, sat_file, download_d): @@ -216,15 +275,17 @@ class XEP_0363(object): @param iq_elt(domish.Element): - """ % (JID_STR_FROM, JID_STR_TO, JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC) + """ % ( + JID_STR_FROM, + JID_STR_TO, + JID_STR_X_TO, + JID_STR_X_CC, + JID_STR_X_BCC, + ) stanza = parseXml(xml.encode("utf-8")) treatments = defer.Deferred() - self.plugin.messageReceivedTrigger(self.host.getClient(PROFILE), stanza, treatments) - data = {'extra': {}} + self.plugin.messageReceivedTrigger( + self.host.getClient(PROFILE), stanza, treatments + ) + data = {"extra": {}} def cb(data): - expected = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC) - msg = 'Expected: %s\nGot: %s' % (expected, data['extra']['addresses']) - self.assertEqual(data['extra']['addresses'], '%s:%s\n%s:%s\n%s:%s\n' % expected, msg) + expected = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC) + msg = "Expected: %s\nGot: %s" % (expected, data["extra"]["addresses"]) + self.assertEqual( + data["extra"]["addresses"], "%s:%s\n%s:%s\n%s:%s\n" % expected, msg + ) treatments.addCallback(cb) return treatments.callback(data) def _get_mess_data(self): - mess_data = {"to": JID(JID_STR_TO), - "type": "chat", - "message": "content", - "extra": {} - } - mess_data["extra"]["address"] = '%s:%s\n%s:%s\n%s:%s\n' % ADDRS + mess_data = { + "to": JID(JID_STR_TO), + "type": "chat", + "message": "content", + "extra": {}, + } + mess_data["extra"]["address"] = "%s:%s\n%s:%s\n%s:%s\n" % ADDRS original_stanza = u"""result as specified in XEP-0363 """ try: - slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, 'slot').next() - put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'put').next()) - get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'get').next()) + slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next() + put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next()) + get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next()) except StopIteration: raise exceptions.DataError(u"Incorrect stanza received from server") slot = Slot(put=put_url, get=get_url) return slot - def _getSlot(self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE): + def _getSlot( + self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE + ): """Get a upload slot This method can be used when uploading is done by the frontend @@ -234,9 +295,11 @@ class XEP_0363(object): @param content_type(unicode, None): MIME type of the content empty string or None to guess automatically """ - filename = filename.replace('/', '_') + filename = filename.replace("/", "_") client = self.host.getClient(profile_key) - return self.getSlot(client, filename, size, content_type or None, upload_jid or None) + return self.getSlot( + client, filename, size, content_type or None, upload_jid or None + ) def getSlot(self, client, filename, size, content_type=None, upload_jid=None): """Get a slot (i.e. download/upload links) @@ -261,19 +324,25 @@ class XEP_0363(object): upload_jid = client.http_upload_service except AttributeError: d = self.getHTTPUploadEntity(profile=client.profile) - d.addCallback(lambda found_entity: self.getSlot(client, filename, size, content_type, found_entity)) + d.addCallback( + lambda found_entity: self.getSlot( + client, filename, size, content_type, found_entity + ) + ) return d else: if upload_jid is None: - raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) + raise failure.Failure( + exceptions.NotFound(u"No HTTP upload entity found") + ) - iq_elt = client.IQ('get') - iq_elt['to'] = upload_jid.full() - request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, 'request')) - request_elt.addElement('filename', content=filename) - request_elt.addElement('size', content=unicode(size)) + iq_elt = client.IQ("get") + iq_elt["to"] = upload_jid.full() + request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request")) + request_elt.addElement("filename", content=filename) + request_elt.addElement("size", content=unicode(size)) if content_type is not None: - request_elt.addElement('content-type', content=content_type) + request_elt.addElement("content-type", content=content_type) d = iq_elt.send() d.addCallback(self._gotSlot, client) @@ -284,8 +353,8 @@ class XEP_0363(object): class XEP_0363_handler(XMPPHandler): implements(iwokkel.IDisco) - def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(NS_HTTP_UPLOAD)] - def getDiscoItems(self, requestor, target, nodeIdentifier=''): + def getDiscoItems(self, requestor, target, nodeIdentifier=""): return [] diff --git a/sat/stdui/ui_contact_list.py b/sat/stdui/ui_contact_list.py index 800e84f7..fe602c42 100644 --- a/sat/stdui/ui_contact_list.py +++ b/sat/stdui/ui_contact_list.py @@ -31,17 +31,34 @@ class ContactList(object): self.host = host self.__add_id = host.registerCallback(self._addContact, with_data=True) self.__update_id = host.registerCallback(self._updateContact, with_data=True) - self.__confirm_delete_id = host.registerCallback(self._getConfirmRemoveXMLUI, with_data=True) + self.__confirm_delete_id = host.registerCallback( + self._getConfirmRemoveXMLUI, with_data=True + ) - host.importMenu((D_("Contacts"), D_("Add contact")), self._getAddDialogXMLUI, security_limit=2, help_string=D_("Add contact")) - host.importMenu((D_("Contacts"), D_("Update contact")), self._getUpdateDialogXMLUI, security_limit=2, help_string=D_("Update contact")) - host.importMenu((D_("Contacts"), D_("Remove contact")), self._getRemoveDialogXMLUI, security_limit=2, help_string=D_("Remove contact")) + host.importMenu( + (D_("Contacts"), D_("Add contact")), + self._getAddDialogXMLUI, + security_limit=2, + help_string=D_("Add contact"), + ) + host.importMenu( + (D_("Contacts"), D_("Update contact")), + self._getUpdateDialogXMLUI, + security_limit=2, + help_string=D_("Update contact"), + ) + host.importMenu( + (D_("Contacts"), D_("Remove contact")), + self._getRemoveDialogXMLUI, + security_limit=2, + help_string=D_("Remove contact"), + ) # FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host - if 'MISC-ACCOUNT' in self.host.plugins: - self.default_host = self.host.plugins['MISC-ACCOUNT'].getNewAccountDomain() + if "MISC-ACCOUNT" in self.host.plugins: + self.default_host = self.host.plugins["MISC-ACCOUNT"].getNewAccountDomain() else: - self.default_host = 'example.net' + self.default_host = "example.net" def getContacts(self, profile): """Return a sorted list of the contacts for that profile @@ -96,11 +113,11 @@ class ContactList(object): """ elts = [] for key in data: - key_elt = Element('jid') - key_elt.setAttribute('name', key) + key_elt = Element("jid") + key_elt.setAttribute("name", key) for value in data[key]: - value_elt = Element('group') - value_elt.setAttribute('name', value) + value_elt = Element("group") + value_elt.setAttribute("name", value) key_elt.childNodes.append(value_elt) elts.append(key_elt) return elts @@ -116,44 +133,50 @@ class ContactList(object): @param profile: %(doc_profile)s @return dict """ - form_ui = xml_tools.XMLUI("form", title=options['title'], submit_id=options['id']) - if 'message' in data: - form_ui.addText(data['message']) - form_ui.addDivider('dash') + form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"]) + if "message" in data: + form_ui.addText(data["message"]) + form_ui.addDivider("dash") - form_ui.addText(options['contact_text']) - if options['id'] == self.__add_id: - contact = data.get(xml_tools.formEscape('contact_jid'), '@%s' % self.default_host) - form_ui.addString('contact_jid', value=contact) - elif options['id'] == self.__update_id: + form_ui.addText(options["contact_text"]) + if options["id"] == self.__add_id: + contact = data.get( + xml_tools.formEscape("contact_jid"), "@%s" % self.default_host + ) + form_ui.addString("contact_jid", value=contact) + elif options["id"] == self.__update_id: contacts = self.getContacts(profile) - list_ = form_ui.addList('contact_jid', options=contacts, selected=contacts[0]) + list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0]) elts = self._data2elts(self.getGroupsOfAllContacts(profile)) - list_.setInternalCallback('groups_of_contact', fields=['contact_jid', 'groups_list'], data_elts=elts) + list_.setInternalCallback( + "groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts + ) - form_ui.addDivider('blank') + form_ui.addDivider("blank") form_ui.addText(_("Select in which groups your contact is:")) selected_groups = [] - if 'selected_groups' in data: - selected_groups = data['selected_groups'] - elif options['id'] == self.__update_id: + if "selected_groups" in data: + selected_groups = data["selected_groups"] + elif options["id"] == self.__update_id: try: selected_groups = self.getGroupsOfContact(contacts[0], profile) except IndexError: pass groups = self.getGroups(selected_groups, profile) - form_ui.addList('groups_list', options=groups, selected=selected_groups, styles=['multi']) + form_ui.addList( + "groups_list", options=groups, selected=selected_groups, styles=["multi"] + ) - adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable='no') + adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable="no") form_ui.addLabel(D_("Add group")) form_ui.addString("add_group") - button = form_ui.addButton('', value=D_('Add')) - button.setInternalCallback('move', fields=['add_group', 'groups_list']) + button = form_ui.addButton("", value=D_("Add")) + button.setInternalCallback("move", fields=["add_group", "groups_list"]) adv_list.end() - form_ui.addDivider('blank') - return {'xmlui': form_ui.toXml()} + form_ui.addDivider("blank") + return {"xmlui": form_ui.toXml()} def _getAddDialogXMLUI(self, data, profile): """Get the dialog for adding contact @@ -162,10 +185,11 @@ class ContactList(object): @param profile: %(doc_profile)s @return dict """ - options = {'id': self.__add_id, - 'title': D_('Add contact'), - 'contact_text': D_("New contact identifier (JID):"), - } + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_("New contact identifier (JID):"), + } return self.getDialogXMLUI(options, {}, profile) def _getUpdateDialogXMLUI(self, data, profile): @@ -176,14 +200,15 @@ class ContactList(object): @return dict """ if not self.getContacts(profile): - _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to update')) - _dialog.addText(_('Your contact list is empty.')) - return {'xmlui': _dialog.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} - options = {'id': self.__update_id, - 'title': D_('Update contact'), - 'contact_text': D_("Which contact do you want to update?"), - } + options = { + "id": self.__update_id, + "title": D_("Update contact"), + "contact_text": D_("Which contact do you want to update?"), + } return self.getDialogXMLUI(options, {}, profile) def _getRemoveDialogXMLUI(self, data, profile): @@ -194,13 +219,17 @@ class ContactList(object): @return dict """ if not self.getContacts(profile): - _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to delete')) - _dialog.addText(_('Your contact list is empty.')) - return {'xmlui': _dialog.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete")) + _dialog.addText(_("Your contact list is empty.")) + return {"xmlui": _dialog.toXml()} - form_ui = xml_tools.XMLUI("form", title=D_('Who do you want to remove from your contacts?'), submit_id=self.__confirm_delete_id) - form_ui.addList('contact_jid', options=self.getContacts(profile)) - return {'xmlui': form_ui.toXml()} + form_ui = xml_tools.XMLUI( + "form", + title=D_("Who do you want to remove from your contacts?"), + submit_id=self.__confirm_delete_id, + ) + form_ui.addList("contact_jid", options=self.getContacts(profile)) + return {"xmlui": form_ui.toXml()} def _getConfirmRemoveXMLUI(self, data, profile): """Get the confirmation dialog for removing contact @@ -209,17 +238,21 @@ class ContactList(object): @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact = data[xml_tools.formEscape('contact_jid')] + contact = data[xml_tools.formEscape("contact_jid")] + def delete_cb(data, profile): - if not C.bool(data.get('cancelled', 'false')): + if not C.bool(data.get("cancelled", "false")): self._deleteContact(jid.JID(contact), profile) return {} + delete_id = self.host.registerCallback(delete_cb, with_data=True, one_shot=True) form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id) - form_ui.addText(D_("Are you sure you want to remove %s from your contact list?") % contact) - return {'xmlui': form_ui.toXml()} + form_ui.addText( + D_("Are you sure you want to remove %s from your contact list?") % contact + ) + return {"xmlui": form_ui.toXml()} def _addContact(self, data, profile): """Add the selected contact @@ -228,18 +261,22 @@ class ContactList(object): @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact_jid_s = data[xml_tools.formEscape('contact_jid')] + contact_jid_s = data[xml_tools.formEscape("contact_jid")] try: contact_jid = jid.JID(contact_jid_s) except (RuntimeError, jid.InvalidFormat, AttributeError): # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted) - data['selected_groups'] = data[xml_tools.formEscape('groups_list')].split('\t') - options = {'id': self.__add_id, - 'title': D_('Add contact'), - 'contact_text': D_('Please enter a valid JID (like "contact@%s"):') % self.default_host, - } + data["selected_groups"] = data[xml_tools.formEscape("groups_list")].split( + "\t" + ) + options = { + "id": self.__add_id, + "title": D_("Add contact"), + "contact_text": D_('Please enter a valid JID (like "contact@%s"):') + % self.default_host, + } return self.getDialogXMLUI(options, data, profile) self.host.addContact(contact_jid, profile_key=profile) return self._updateContact(data, profile) # after adding, updating @@ -251,12 +288,12 @@ class ContactList(object): @param profile: %(doc_profile)s @return dict """ - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} - contact_jid = jid.JID(data[xml_tools.formEscape('contact_jid')]) + contact_jid = jid.JID(data[xml_tools.formEscape("contact_jid")]) # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted) - groups = data[xml_tools.formEscape('groups_list')].split('\t') - self.host.updateContact(contact_jid, name='', groups=groups, profile_key=profile) + groups = data[xml_tools.formEscape("groups_list")].split("\t") + self.host.updateContact(contact_jid, name="", groups=groups, profile_key=profile) return {} def _deleteContact(self, contact_jid, profile): diff --git a/sat/stdui/ui_profile_manager.py b/sat/stdui/ui_profile_manager.py index ef3fb7d0..316b61dc 100644 --- a/sat/stdui/ui_profile_manager.py +++ b/sat/stdui/ui_profile_manager.py @@ -21,6 +21,7 @@ from sat.core.i18n import D_ from sat.core.constants import Const as C from sat.core.log import getLogger + log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools @@ -35,9 +36,15 @@ class ProfileManager(object): self.host = host self.profile_ciphers = {} self._sessions = ProfileSessions() - host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True) - host.registerCallback(self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True) - self.__new_xmpp_passwd_id = host.registerCallback(self._changeXMPPPasswordCb, with_data=True) + host.registerCallback( + self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True + ) + host.registerCallback( + self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True + ) + self.__new_xmpp_passwd_id = host.registerCallback( + self._changeXMPPPasswordCb, with_data=True + ) def _startSessionEb(self, fail, first, profile): """Errback method for startSession during profile authentication @@ -49,66 +56,93 @@ class ProfileManager(object): """ if first: # first call, we ask for the password - form_ui = xml_tools.XMLUI("form", title=D_('Profile password for {}').format(profile), submit_id='') - form_ui.addPassword('profile_password', value='') + form_ui = xml_tools.XMLUI( + "form", title=D_("Profile password for {}").format(profile), submit_id="" + ) + form_ui.addPassword("profile_password", value="") d = xml_tools.deferredUI(self.host, form_ui, chained=True) d.addCallback(self._authenticateProfile, profile) - return {'xmlui': form_ui.toXml()} + return {"xmlui": form_ui.toXml()} assert profile is None if fail.check(exceptions.PasswordError): - dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) + dialog = xml_tools.XMLUI("popup", title=D_("Connection error")) dialog.addText(D_("The provided profile password doesn't match.")) else: log.error(u"Unexpected exceptions: {}".format(fail)) - dialog = xml_tools.XMLUI('popup', title=D_('Internal error')) + dialog = xml_tools.XMLUI("popup", title=D_("Internal error")) dialog.addText(D_(u"Internal error: {}".format(fail))) - return {'xmlui': dialog.toXml(), 'validated': C.BOOL_FALSE} + return {"xmlui": dialog.toXml(), "validated": C.BOOL_FALSE} def _authenticateProfile(self, data, profile): - if C.bool(data.get('cancelled', 'false')): + if C.bool(data.get("cancelled", "false")): return {} if self.host.memory.isSessionStarted(profile): - return {'validated': C.BOOL_TRUE} + return {"validated": C.BOOL_TRUE} try: - password = data[xml_tools.formEscape('profile_password')] + password = data[xml_tools.formEscape("profile_password")] except KeyError: # first request, we try empty password - password = '' + password = "" first = True eb_profile = profile else: first = False eb_profile = None d = self.host.memory.startSession(password, profile) - d.addCallback(lambda dummy: {'validated': C.BOOL_TRUE}) + d.addCallback(lambda dummy: {"validated": C.BOOL_TRUE}) d.addErrback(self._startSessionEb, first, eb_profile) return d def _changeXMPPPassword(self, data, profile): session_data = self._sessions.profileGetUnique(profile) if not session_data: - server = self.host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) + server = self.host.memory.getParamA( + C.FORCE_SERVER_PARAM, "Connection", profile_key=profile + ) if not server: - server = jid.parse(self.host.memory.getParamA('JabberID', "Connection", profile_key=profile))[1] - session_id, session_data = self._sessions.newSession({'count': 0, 'server': server}, profile=profile) - if session_data['count'] > 2: # 3 attempts with a new password after the initial try + server = jid.parse( + self.host.memory.getParamA( + "JabberID", "Connection", profile_key=profile + ) + )[1] + session_id, session_data = self._sessions.newSession( + {"count": 0, "server": server}, profile=profile + ) + if ( + session_data["count"] > 2 + ): # 3 attempts with a new password after the initial try self._sessions.profileDelUnique(profile) - _dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) - _dialog.addText(D_("Can't connect to %s. Please check your connection details.") % session_data['server']) - return {'xmlui': _dialog.toXml()} - session_data['count'] += 1 - counter = ' (%d)' % session_data['count'] if session_data['count'] > 1 else '' - title = D_('XMPP password for %(profile)s%(counter)s') % {'profile': profile, 'counter': counter} - form_ui = xml_tools.XMLUI("form", title=title, submit_id=self.__new_xmpp_passwd_id) - form_ui.addText(D_("Can't connect to %s. Please check your connection details or try with another password.") % session_data['server']) - form_ui.addPassword('xmpp_password', value='') - return {'xmlui': form_ui.toXml()} + _dialog = xml_tools.XMLUI("popup", title=D_("Connection error")) + _dialog.addText( + D_("Can't connect to %s. Please check your connection details.") + % session_data["server"] + ) + return {"xmlui": _dialog.toXml()} + session_data["count"] += 1 + counter = " (%d)" % session_data["count"] if session_data["count"] > 1 else "" + title = D_("XMPP password for %(profile)s%(counter)s") % { + "profile": profile, + "counter": counter, + } + form_ui = xml_tools.XMLUI( + "form", title=title, submit_id=self.__new_xmpp_passwd_id + ) + form_ui.addText( + D_( + "Can't connect to %s. Please check your connection details or try with another password." + ) + % session_data["server"] + ) + form_ui.addPassword("xmpp_password", value="") + return {"xmlui": form_ui.toXml()} def _changeXMPPPasswordCb(self, data, profile): - xmpp_password = data[xml_tools.formEscape('xmpp_password')] - d = self.host.memory.setParam("Password", xmpp_password, "Connection", profile_key=profile) + xmpp_password = data[xml_tools.formEscape("xmpp_password")] + d = self.host.memory.setParam( + "Password", xmpp_password, "Connection", profile_key=profile + ) d.addCallback(lambda dummy: self.host.connect(profile)) d.addCallback(lambda dummy: {}) d.addErrback(lambda dummy: self._changeXMPPPassword({}, profile)) diff --git a/sat/test/constants.py b/sat/test/constants.py index 37cbe4d5..c9251fc9 100644 --- a/sat/test/constants.py +++ b/sat/test/constants.py @@ -23,10 +23,22 @@ from twisted.words.protocols.jabber import jid class Const(object): - PROF_KEY_NONE = '@NONE@' + PROF_KEY_NONE = "@NONE@" - PROFILE = ['test_profile', 'test_profile2', 'test_profile3', 'test_profile4', 'test_profile5'] - JID_STR = [u"test@example.org/SàT", u"sender@example.net/house", u"sender@example.net/work", u"sender@server.net/res", u"xxx@server.net/res"] + PROFILE = [ + "test_profile", + "test_profile2", + "test_profile3", + "test_profile4", + "test_profile5", + ] + JID_STR = [ + u"test@example.org/SàT", + u"sender@example.net/house", + u"sender@example.net/work", + u"sender@server.net/res", + u"xxx@server.net/res", + ] JID = [jid.JID(jid_s) for jid_s in JID_STR] PROFILE_DICT = {} diff --git a/sat/test/helpers_plugins.py b/sat/test/helpers_plugins.py index 96582e75..56cbb5d6 100644 --- a/sat/test/helpers_plugins.py +++ b/sat/test/helpers_plugins.py @@ -57,8 +57,12 @@ class FakeMUCClient(object): if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid] - roster.setdefault(other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile])) + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + room_jid + ] + roster.setdefault( + other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile]) + ) for other_nick in other_room.roster: roster.setdefault(other_nick, other_room.roster[other_nick]) except (AttributeError, KeyError): @@ -80,8 +84,12 @@ class FakeMUCClient(object): if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid] - other_room.roster.setdefault(room.nick, User(room.nick, C.PROFILE_DICT[profile])) + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + room_jid + ] + other_room.roster.setdefault( + room.nick, User(room.nick, C.PROFILE_DICT[profile]) + ) except (AttributeError, KeyError): pass @@ -101,7 +109,9 @@ class FakeMUCClient(object): if other_profile == profile: continue try: - other_room = self.plugin_parent.clients[other_profile].joined_rooms[roomJID] + other_room = self.plugin_parent.clients[other_profile].joined_rooms[ + roomJID + ] del other_room.roster[room.nick] except (AttributeError, KeyError): pass @@ -110,14 +120,13 @@ class FakeMUCClient(object): class FakeXEP_0045(plugin_xep_0045.XEP_0045): - def __init__(self, host): self.host = host self.clients = {} for profile in C.PROFILE: self.clients[profile] = FakeMUCClient(self) - def join(self, room_jid, nick, options={}, profile_key='@DEFAULT@'): + def join(self, room_jid, nick, options={}, profile_key="@DEFAULT@"): """ @param roomJID: the room JID @param nick: nick to be used in the room @@ -140,7 +149,7 @@ class FakeXEP_0045(plugin_xep_0045.XEP_0045): self.join(muc_jid, nick, profile_key=profile) return self.getNick(muc_index, user_index) - def leave(self, room_jid, profile_key='@DEFAULT@'): + def leave(self, room_jid, profile_key="@DEFAULT@"): """ @param roomJID: the room JID @param profile_key: the profile of the user leaving the room @@ -174,7 +183,7 @@ class FakeXEP_0045(plugin_xep_0045.XEP_0045): try: return self.getRoomNick(C.MUC[muc_index], C.PROFILE[user_index]) except (KeyError, AttributeError): - return '' + return "" def getNickOfUser(self, muc_index, user_index, profile_index, secure=True): try: @@ -185,11 +194,10 @@ class FakeXEP_0045(plugin_xep_0045.XEP_0045): class FakeXEP_0249(object): - def __init__(self, host): self.host = host - def invite(self, target, room, options={}, profile_key='@DEFAULT@'): + def invite(self, target, room, options={}, profile_key="@DEFAULT@"): """ Invite a user to a room. To accept the invitation from a test, just call FakeXEP_0045.joinRoom (no need to have a dedicated method). @@ -202,15 +210,13 @@ class FakeXEP_0249(object): class FakeSatPubSubClient(object): - def __init__(self, host, parent_plugin): self.host = host self.parent_plugin = parent_plugin self.__items = OrderedDict() self.__rsm_responses = {} - def createNode(self, service, nodeIdentifier=None, options=None, - sender=None): + def createNode(self, service, nodeIdentifier=None, options=None, sender=None): return defer.succeed(None) def deleteNode(self, service, nodeIdentifier, sender=None): @@ -220,12 +226,17 @@ class FakeSatPubSubClient(object): pass return defer.succeed(None) - def subscribe(self, service, nodeIdentifier, subscriber, - options=None, sender=None): + def subscribe(self, service, nodeIdentifier, subscriber, options=None, sender=None): return defer.succeed(None) - def unsubscribe(self, service, nodeIdentifier, subscriber, - subscriptionIdentifier=None, sender=None): + def unsubscribe( + self, + service, + nodeIdentifier, + subscriber, + subscriptionIdentifier=None, + sender=None, + ): return defer.succeed(None) def publish(self, service, nodeIdentifier, items=None, sender=None): @@ -234,7 +245,7 @@ class FakeSatPubSubClient(object): def replace(item_obj): index = 0 for current in node: - if current['id'] == item_obj['id']: + if current["id"] == item_obj["id"]: node[index] = item_obj return True index += 1 @@ -246,22 +257,30 @@ class FakeSatPubSubClient(object): node.append(item_obj) return defer.succeed(None) - def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None, - subscriptionIdentifier=None, sender=None, ext_data=None): + def items( + self, + service, + nodeIdentifier, + maxItems=None, + itemIdentifiers=None, + subscriptionIdentifier=None, + sender=None, + ext_data=None, + ): try: items = self.__items[nodeIdentifier] except KeyError: items = [] if ext_data: - assert('id' in ext_data) - if 'rsm' in ext_data: - args = (0, items[0]['id'], items[-1]['id']) if items else () - self.__rsm_responses[ext_data['id']] = RSMResponse(len(items), *args) + assert "id" in ext_data + if "rsm" in ext_data: + args = (0, items[0]["id"], items[-1]["id"]) if items else () + self.__rsm_responses[ext_data["id"]] = RSMResponse(len(items), *args) return defer.succeed(items) def retractItems(self, service, nodeIdentifier, itemIdentifiers, sender=None): node = self.__items[nodeIdentifier] - for item in [item for item in node if item['id'] in itemIdentifiers]: + for item in [item for item in node if item["id"] in itemIdentifiers]: node.remove(item) return defer.succeed(None) diff --git a/sat/test/test_helpers_plugins.py b/sat/test/test_helpers_plugins.py index c079a211..346ee315 100644 --- a/sat/test/test_helpers_plugins.py +++ b/sat/test/test_helpers_plugins.py @@ -25,93 +25,100 @@ from sat.test import helpers_plugins class FakeXEP_0045Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = helpers_plugins.FakeXEP_0045(self.host) def test_joinRoom(self): self.plugin.joinRoom(0, 0) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('', self.plugin.getNick(0, 1)) + self.assertEqual("", self.plugin.getNick(0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('', self.plugin.getNick(0, 2)) + self.assertEqual("", self.plugin.getNick(0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 1) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('', self.plugin.getNick(0, 2)) + self.assertEqual("", self.plugin.getNick(0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 2) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1)) # Const.JID[2] is in the roster for Const.PROFILE[1] - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 1, 1) + ) # Const.JID[2] is in the roster for Const.PROFILE[1] + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('sender', self.plugin.getNick(0, 2)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2)) # Const.JID[1] is in the roster for Const.PROFILE[2] + self.assertEqual("sender", self.plugin.getNick(0, 2)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 2, 2) + ) # Const.JID[1] is in the roster for Const.PROFILE[2] self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('', self.plugin.getNick(0, 3)) + self.assertEqual("", self.plugin.getNick(0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) self.plugin.joinRoom(0, 3) - self.assertEqual('test', self.plugin.getNick(0, 0)) + self.assertEqual("test", self.plugin.getNick(0, 0)) self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0)) - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 0)) - self.assertEqual('sender', self.plugin.getNick(0, 1)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1)) # Const.JID[2] is in the roster for Const.PROFILE[1] - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1)) - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 1)) - self.assertEqual('sender', self.plugin.getNick(0, 2)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2)) # Const.JID[1] is in the roster for Const.PROFILE[2] - self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 2)) - self.assertEqual('sender_', self.plugin.getNick(0, 3)) - self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 3)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 3)) - self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0)) + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 0)) + self.assertEqual("sender", self.plugin.getNick(0, 1)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 1, 1) + ) # Const.JID[2] is in the roster for Const.PROFILE[1] + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1)) + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 1)) + self.assertEqual("sender", self.plugin.getNick(0, 2)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2)) + self.assertEqual( + "sender", self.plugin.getNickOfUser(0, 2, 2) + ) # Const.JID[1] is in the roster for Const.PROFILE[2] + self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 2)) + self.assertEqual("sender_", self.plugin.getNick(0, 3)) + self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 3)) + self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 3)) self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3)) diff --git a/sat/test/test_memory.py b/sat/test/test_memory.py index 29a2f0a0..1eb1530b 100644 --- a/sat/test/test_memory.py +++ b/sat/test/test_memory.py @@ -26,7 +26,6 @@ from xml.dom import minidom class MemoryTest(unittest.TestCase): - def setUp(self): self.host = helpers.FakeSAT() @@ -37,14 +36,19 @@ class MemoryTest(unittest.TestCase): @param security_level: security level of the parameters @return (str) """ + def getParam(name): return """ - """ % {'param_name': name, - 'param_label': _(name), - 'security': '' if security_level is None else ('security="%d"' % security_level) - } - params = '' + """ % { + "param_name": name, + "param_label": _(name), + "security": "" + if security_level is None + else ('security="%d"' % security_level), + } + + params = "" if "1" in param: params += getParam(Const.ENABLE_UNIBOX_PARAM) if "2" in param: @@ -60,9 +64,9 @@ class MemoryTest(unittest.TestCase): """ % { - 'category_name': Const.COMPOSITION_KEY, - 'category_label': _(Const.COMPOSITION_KEY), - 'params': params + "category_name": Const.COMPOSITION_KEY, + "category_label": _(Const.COMPOSITION_KEY), + "params": params, } def _paramExists(self, param="1", src=None): @@ -86,7 +90,10 @@ class MemoryTest(unittest.TestCase): if type_node.nodeName not in ("individual", "general", "params"): continue for cat_node in type_node.childNodes: - if cat_node.nodeName != "category" or cat_node.getAttribute("name") != category: + if ( + cat_node.nodeName != "category" + or cat_node.getAttribute("name") != category + ): continue for param in cat_node.childNodes: if param.nodeName == "param" and param.getAttribute("name") == name: @@ -100,7 +107,11 @@ class MemoryTest(unittest.TestCase): @param exists (boolean): True to assert the param exists, False to assert it doesn't @param deferred (boolean): True if this method is called from a Deferred callback """ - msg = "Expected parameter not found!\n" if exists else "Unexpected parameter found!\n" + msg = ( + "Expected parameter not found!\n" + if exists + else "Unexpected parameter found!\n" + ) if deferred: # in this stack we can see the line where the error came from, # if limit=5, 6 is not enough you can increase the value @@ -116,21 +127,25 @@ class MemoryTest(unittest.TestCase): def assertParamExists_async(self, src, param="1"): """@param src: a deferred result from Memory.getParams""" - self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), True, True) + self.assertParam_generic( + param, minidom.parseString(src.encode("utf-8")), True, True + ) def assertParamNotExists_async(self, src, param="1"): """@param src: a deferred result from Memory.getParams""" - self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), False, True) + self.assertParam_generic( + param, minidom.parseString(src.encode("utf-8")), False, True + ) - def _getParams(self, security_limit, app='', profile_key='@NONE@'): + def _getParams(self, security_limit, app="", profile_key="@NONE@"): """Get the parameters accessible with the given security limit and application name. @param security_limit (int): the security limit @param app (str): empty string or "libervia" @param profile_key """ - if profile_key == '@NONE@': - profile_key = '@DEFAULT@' + if profile_key == "@NONE@": + profile_key = "@DEFAULT@" return self.host.memory.params.getParams(security_limit, app, profile_key) def test_updateParams(self): @@ -141,14 +156,17 @@ class MemoryTest(unittest.TestCase): previous = self.host.memory.params.dom.cloneNode(True) # now check if it is really updated and not duplicated self.host.memory.updateParams(self._getParamXML()) - self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8")) + self.assertEqual( + previous.toxml().encode("utf-8"), + self.host.memory.params.dom.toxml().encode("utf-8"), + ) self.host.memory.reinit() # check successive updates (without intersection) - self.host.memory.updateParams(self._getParamXML('1')) + self.host.memory.updateParams(self._getParamXML("1")) self.assertParamExists("1") self.assertParamNotExists("2") - self.host.memory.updateParams(self._getParamXML('2')) + self.host.memory.updateParams(self._getParamXML("2")) self.assertParamExists("1") self.assertParamExists("2") @@ -156,19 +174,22 @@ class MemoryTest(unittest.TestCase): self.host.memory.reinit() # check successive updates (with intersection) - self.host.memory.updateParams(self._getParamXML('1')) + self.host.memory.updateParams(self._getParamXML("1")) self.assertParamExists("1") self.assertParamNotExists("2") - self.host.memory.updateParams(self._getParamXML('12')) + self.host.memory.updateParams(self._getParamXML("12")) self.assertParamExists("1") self.assertParamExists("2") # successive updates with or without intersection should have the same result - self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8")) + self.assertEqual( + previous.toxml().encode("utf-8"), + self.host.memory.params.dom.toxml().encode("utf-8"), + ) self.host.memory.reinit() # one update with two params in a new category - self.host.memory.updateParams(self._getParamXML('12')) + self.host.memory.updateParams(self._getParamXML("12")) self.assertParamExists("1") self.assertParamExists("2") @@ -196,7 +217,6 @@ class MemoryTest(unittest.TestCase): return self._getParams(1).addCallback(self.assertParamExists_async) def test_paramsRegisterApp(self): - def register(xml, security_limit, app): """ @param xml: XML definition of the parameters to be added @@ -271,17 +291,23 @@ class MemoryTest(unittest.TestCase): self.host.memory.reinit() params = self._getParamXML(security_level=1) self.host.memory.paramsRegisterApp(params, 1, Const.APP_NAME) - self._getParams(1, '').addCallback(self.assertParamExists_async) + self._getParams(1, "").addCallback(self.assertParamExists_async) self._getParams(1, Const.APP_NAME).addCallback(self.assertParamExists_async) - self._getParams(1, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async) + self._getParams(1, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async + ) # the same with several parameters registered at the same time self.host.memory.reinit() - params = self._getParamXML('12', security_level=0) + params = self._getParamXML("12", security_level=0) self.host.memory.paramsRegisterApp(params, 5, Const.APP_NAME) - self._getParams(5, '').addCallback(self.assertParamExists_async) - self._getParams(5, '').addCallback(self.assertParamExists_async, "2") + self._getParams(5, "").addCallback(self.assertParamExists_async) + self._getParams(5, "").addCallback(self.assertParamExists_async, "2") self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async) self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async, "2") - self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async) - return self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async, "2") + self._getParams(5, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async + ) + return self._getParams(5, "another_dummy_frontend").addCallback( + self.assertParamNotExists_async, "2" + ) diff --git a/sat/test/test_memory_crypto.py b/sat/test/test_memory_crypto.py index ccde268d..e6acee89 100644 --- a/sat/test/test_memory_crypto.py +++ b/sat/test/test_memory_crypto.py @@ -30,11 +30,10 @@ from twisted.internet import defer def getRandomUnicode(len): """Return a random unicode string""" - return u''.join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len)) + return u"".join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len)) class CryptoTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() diff --git a/sat/test/test_plugin_misc_groupblog.py b/sat/test/test_plugin_misc_groupblog.py index e6d1bf18..7c114cdc 100644 --- a/sat/test/test_plugin_misc_groupblog.py +++ b/sat/test/test_plugin_misc_groupblog.py @@ -31,27 +31,32 @@ from twisted.internet import defer from twisted.words.protocols.jabber import jid -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" DO_NOT_COUNT_COMMENTS = -1 -SERVICE = u'pubsub.example.com' -PUBLISHER = u'test@example.org' -OTHER_PUBLISHER = u'other@xmpp.net' -NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=PUBLISHER) -OTHER_NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=OTHER_PUBLISHER) -ITEM_ID_1 = u'c745a688-9b02-11e3-a1a3-c0143dd4fe51' -COMMENT_ID_1 = u'd745a688-9b02-11e3-a1a3-c0143dd4fe52' -COMMENT_ID_2 = u'e745a688-9b02-11e3-a1a3-c0143dd4fe53' +SERVICE = u"pubsub.example.com" +PUBLISHER = u"test@example.org" +OTHER_PUBLISHER = u"other@xmpp.net" +NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=PUBLISHER) +OTHER_NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=OTHER_PUBLISHER) +ITEM_ID_1 = u"c745a688-9b02-11e3-a1a3-c0143dd4fe51" +COMMENT_ID_1 = u"d745a688-9b02-11e3-a1a3-c0143dd4fe52" +COMMENT_ID_2 = u"e745a688-9b02-11e3-a1a3-c0143dd4fe53" def COMMENTS_NODE_ID(publisher=PUBLISHER): - return u'urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}'.format(id=ITEM_ID_1, publisher=publisher) + return u"urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}".format( + id=ITEM_ID_1, publisher=publisher + ) def COMMENTS_NODE_URL(publisher=PUBLISHER): - return u'xmpp:{service}?node={node}'.format(service=SERVICE, id=ITEM_ID_1, - node=COMMENTS_NODE_ID(publisher).replace(':', '%3A').replace('@', '%40')) + return u"xmpp:{service}?node={node}".format( + service=SERVICE, + id=ITEM_ID_1, + node=COMMENTS_NODE_ID(publisher).replace(":", "%3A").replace("@", "%40"), + ) def ITEM(publisher=PUBLISHER): @@ -68,7 +73,12 @@ def ITEM(publisher=PUBLISHER): - """.format(ns=NS_PUBSUB, id=ITEM_ID_1, publisher=publisher, comments_node_url=COMMENTS_NODE_URL(publisher)) + """.format( + ns=NS_PUBSUB, + id=ITEM_ID_1, + publisher=publisher, + comments_node_url=COMMENTS_NODE_URL(publisher), + ) def COMMENT(id_=COMMENT_ID_1): @@ -84,35 +94,41 @@ def COMMENT(id_=COMMENT_ID_1): - """.format(ns=NS_PUBSUB, id=id_, publisher=PUBLISHER) + """.format( + ns=NS_PUBSUB, id=id_, publisher=PUBLISHER + ) def ITEM_DATA(id_=ITEM_ID_1, count=0): - res = {'id': ITEM_ID_1, - 'type': 'main_item', - 'content': 'The Uses of This World', - 'author': PUBLISHER, - 'updated': '1071251243.0', - 'published': '1071251243.0', - 'service': SERVICE, - 'comments': COMMENTS_NODE_URL_1, - 'comments_service': SERVICE, - 'comments_node': COMMENTS_NODE_ID_1} + res = { + "id": ITEM_ID_1, + "type": "main_item", + "content": "The Uses of This World", + "author": PUBLISHER, + "updated": "1071251243.0", + "published": "1071251243.0", + "service": SERVICE, + "comments": COMMENTS_NODE_URL_1, + "comments_service": SERVICE, + "comments_node": COMMENTS_NODE_ID_1, + } if count != DO_NOT_COUNT_COMMENTS: - res.update({'comments_count': unicode(count)}) + res.update({"comments_count": unicode(count)}) return res def COMMENT_DATA(id_=COMMENT_ID_1): - return {'id': id_, - 'type': 'comment', - 'content': 'The Uses of This World', - 'author': PUBLISHER, - 'updated': '1071251243.0', - 'published': '1071251243.0', - 'service': SERVICE, - 'node': COMMENTS_NODE_ID_1, - 'verified_publisher': 'false'} + return { + "id": id_, + "type": "comment", + "content": "The Uses of This World", + "author": PUBLISHER, + "updated": "1071251243.0", + "published": "1071251243.0", + "service": SERVICE, + "node": COMMENTS_NODE_ID_1, + "verified_publisher": "false", + } COMMENTS_NODE_ID_1 = COMMENTS_NODE_ID() @@ -128,19 +144,21 @@ COMMENT_2 = COMMENT(COMMENT_ID_2) def ITEM_DATA_1(count=0): return ITEM_DATA(count=count) + COMMENT_DATA_1 = COMMENT_DATA() COMMENT_DATA_2 = COMMENT_DATA(COMMENT_ID_2) class XEP_groupblogTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() - self.host.plugins['XEP-0060'] = plugin_xep_0060.XEP_0060(self.host) - self.host.plugins['XEP-0163'] = plugin_xep_0163.XEP_0163(self.host) + self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host) + self.host.plugins["XEP-0163"] = plugin_xep_0163.XEP_0163(self.host) reload(plugin_misc_text_syntaxes) # reload the plugin to avoid conflict error - self.host.plugins['TEXT-SYNTAXES'] = plugin_misc_text_syntaxes.TextSyntaxes(self.host) - self.host.plugins['XEP-0277'] = plugin_xep_0277.XEP_0277(self.host) + self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes( + self.host + ) + self.host.plugins["XEP-0277"] = plugin_xep_0277.XEP_0277(self.host) self.plugin = plugin_misc_groupblog.GroupBlog(self.host) self.plugin._initialise = self._initialise self.__initialised = False @@ -151,8 +169,10 @@ class XEP_groupblogTest(helpers.SatTestCase): client = self.host.getClient(profile) if not self.__initialised: client.item_access_pubsub = jid.JID(SERVICE) - xep_0060 = self.host.plugins['XEP-0060'] - client.pubsub_client = helpers_plugins.FakeSatPubSubClient(self.host, xep_0060) + xep_0060 = self.host.plugins["XEP-0060"] + client.pubsub_client = helpers_plugins.FakeSatPubSubClient( + self.host, xep_0060 + ) client.pubsub_client.parent = client self.psclient = client.pubsub_client helpers.FakeSAT.getDiscoItems = self.psclient.service_getDiscoItems @@ -167,19 +187,30 @@ class XEP_groupblogTest(helpers.SatTestCase): self._initialise(C.PROFILE[0]) d = self.psclient.items(SERVICE, NODE_ID) d.addCallback(lambda items: self.assertEqual(len(items), 0)) - d.addCallback(lambda dummy: self.plugin.sendGroupBlog('PUBLIC', [], 'test', {}, C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.sendGroupBlog( + "PUBLIC", [], "test", {}, C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) return d.addCallback(lambda items: self.assertEqual(len(items), 1)) def test_deleteGroupBlog(self): pub_data = (SERVICE, NODE_ID, ITEM_ID_1) - self.host.bridge.expectCall('personalEvent', C.JID_STR[0], - "MICROBLOG_DELETE", - {'type': 'main_item', 'id': ITEM_ID_1}, - C.PROFILE[0]) + self.host.bridge.expectCall( + "personalEvent", + C.JID_STR[0], + "MICROBLOG_DELETE", + {"type": "main_item", "id": ITEM_ID_1}, + C.PROFILE[0], + ) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.deleteGroupBlog(pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.deleteGroupBlog( + pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0] + ) + ) return d.addCallback(self.assertEqual, None) def test_updateGroupBlog(self): @@ -188,71 +219,155 @@ class XEP_groupblogTest(helpers.SatTestCase): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.updateGroupBlog(pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.updateGroupBlog( + pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) - return d.addCallback(lambda items: self.assertEqual(''.join(items[0].entry.title.children), new_text)) + return d.addCallback( + lambda items: self.assertEqual( + "".join(items[0].entry.title.children), new_text + ) + ) def test_sendGroupBlogComment(self): self._initialise(C.PROFILE[0]) d = self.psclient.items(SERVICE, NODE_ID) d.addCallback(lambda items: self.assertEqual(len(items), 0)) - d.addCallback(lambda dummy: self.plugin.sendGroupBlogComment(COMMENTS_NODE_URL_1, 'test', {}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.sendGroupBlogComment( + COMMENTS_NODE_URL_1, "test", {}, profile_key=C.PROFILE[0] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) return d.addCallback(lambda items: self.assertEqual(len(items), 1)) def test_getGroupBlogs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0]) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsNoCount(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, count_comments=False, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, count_comments=False, profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithIDs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithRSM(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, rsm_data={'max_': 1}, profile_key=C.PROFILE[0])) - result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogs( + PUBLISHER, rsm_data={"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0])) - result = ([(ITEM_DATA_1(1), ([COMMENT_DATA_1], - {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1}))], - {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsWithComments( + PUBLISHER, [], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ + ( + ITEM_DATA_1(1), + ( + [COMMENT_DATA_1], + { + "count": "1", + "index": "0", + "first": COMMENT_ID_1, + "last": COMMENT_ID_1, + }, + ), + ) + ], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsWithComments2(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0])) - result = ([(ITEM_DATA_1(2), ([COMMENT_DATA_1, COMMENT_DATA_2], - {'count': '2', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_2}))], - {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsWithComments( + PUBLISHER, [], profile_key=C.PROFILE[0] + ) + ) + result = ( + [ + ( + ITEM_DATA_1(2), + ( + [COMMENT_DATA_1, COMMENT_DATA_2], + { + "count": "2", + "index": "0", + "first": COMMENT_ID_1, + "last": COMMENT_ID_2, + }, + ), + ) + ], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_getGroupBlogsAtom(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getGroupBlogsAtom(PUBLISHER, {'max_': 1}, profile_key=C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogsAtom( + PUBLISHER, {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) def cb(atom): self.assertIsInstance(atom, unicode) @@ -263,11 +378,22 @@ class XEP_groupblogTest(helpers.SatTestCase): def test_getMassiveGroupBlogs(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0])) - result = {PUBLISHER: ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})} + d.addCallback( + lambda dummy: self.plugin.getMassiveGroupBlogs( + "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = { + PUBLISHER: ( + [ITEM_DATA_1()], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) + } def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(clean) @@ -276,12 +402,27 @@ class XEP_groupblogTest(helpers.SatTestCase): def test_getMassiveGroupBlogsWithComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) - d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0])) - result = {PUBLISHER: ([ITEM_DATA_1(2)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})} + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) + d.addCallback( + lambda dummy: self.plugin.getMassiveGroupBlogs( + "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = { + PUBLISHER: ( + [ITEM_DATA_1(2)], + {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1}, + ) + } def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(clean) @@ -290,9 +431,18 @@ class XEP_groupblogTest(helpers.SatTestCase): def test_getGroupBlogComments(self): self._initialise(C.PROFILE[0]) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])) - d.addCallback(lambda dummy: self.plugin.getGroupBlogComments(SERVICE, COMMENTS_NODE_ID_1, {'max_': 1}, profile_key=C.PROFILE[0])) - result = ([COMMENT_DATA_1], {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1}) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]) + ) + d.addCallback( + lambda dummy: self.plugin.getGroupBlogComments( + SERVICE, COMMENTS_NODE_ID_1, {"max_": 1}, profile_key=C.PROFILE[0] + ) + ) + result = ( + [COMMENT_DATA_1], + {"count": "1", "index": "0", "first": COMMENT_ID_1, "last": COMMENT_ID_1}, + ) return d.addCallback(self.assertEqual, result) def test_subscribeGroupBlog(self): @@ -302,11 +452,17 @@ class XEP_groupblogTest(helpers.SatTestCase): def test_massiveSubscribeGroupBlogs(self): self._initialise(C.PROFILE[0]) - d = self.plugin.massiveSubscribeGroupBlogs('JID', [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0]) + d = self.plugin.massiveSubscribeGroupBlogs( + "JID", [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0] + ) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@subscriptions@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@subscriptions@" + SERVICE + ] return res d.addCallback(clean) @@ -317,21 +473,33 @@ class XEP_groupblogTest(helpers.SatTestCase): self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogs(C.PROFILE[0])) @@ -353,21 +521,33 @@ class XEP_groupblogTest(helpers.SatTestCase): self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsComments(C.PROFILE[0])) @@ -388,24 +568,38 @@ class XEP_groupblogTest(helpers.SatTestCase): self._initialise(C.PROFILE[0]) self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER)) d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1]) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])) - d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2])) + d.addCallback( + lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]) + ) + d.addCallback( + lambda dummy: self.psclient.publish( + SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2] + ) + ) d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID)) d.addCallback(lambda items: self.assertEqual(len(items), 1)) d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2)) d.addCallback(lambda items: self.assertEqual(len(items), 2)) def clean(res): - del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE] + del self.host.plugins["XEP-0060"].node_cache[ + C.PROFILE[0] + "@found@" + SERVICE + ] return res - d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0])) + d.addCallback( + lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0]) + ) d.addCallback(clean) d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID)) diff --git a/sat/test/test_plugin_misc_radiocol.py b/sat/test/test_plugin_misc_radiocol.py index cc9f6db4..0fa631f8 100644 --- a/sat/test/test_plugin_misc_radiocol.py +++ b/sat/test/test_plugin_misc_radiocol.py @@ -39,7 +39,9 @@ try: from mutagen.easyid3 import EasyID3 from mutagen.id3 import ID3NoHeaderError except ImportError: - raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen") + raise exceptions.MissingModule( + u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen" + ) import uuid import os @@ -49,35 +51,38 @@ import shutil ROOM_JID = JID(Const.MUC_STR[0]) PROFILE = Const.PROFILE[0] -REFEREE_FULL = JID(ROOM_JID.userhost() + '/' + Const.JID[0].user) +REFEREE_FULL = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user) PLAYERS_INDICES = [0, 1, 3] # referee included OTHER_PROFILES = [Const.PROFILE[1], Const.PROFILE[3]] OTHER_PLAYERS = [Const.JID[1], Const.JID[3]] class RadiocolTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() def reinit(self): self.host.reinit() - self.host.plugins['ROOM-GAME'] = plugin_room_game.RoomGame(self.host) + self.host.plugins["ROOM-GAME"] = plugin_room_game.RoomGame(self.host) self.plugin = plugin.Radiocol(self.host) # must be init after ROOM-GAME self.plugin.testing = True - self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host) - self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host) + self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045( + self.host + ) + self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249( + self.host + ) for profile in Const.PROFILE: self.host.getClient(profile) # init self.host.profiles[profile] self.songs = [] self.playlist = [] - self.sound_dir = self.host.memory.getConfig('', 'media_dir') + '/test/sound/' + self.sound_dir = self.host.memory.getConfig("", "media_dir") + "/test/sound/" try: for filename in os.listdir(self.sound_dir): - if filename.endswith('.ogg') or filename.endswith('.mp3'): + if filename.endswith(".ogg") or filename.endswith(".mp3"): self.songs.append(filename) except OSError: - raise SkipTest('The sound samples in sat_media/test/sound were not found') + raise SkipTest("The sound samples in sat_media/test/sound were not found") def _buildPlayers(self, players=[]): """@return: the "started" content built with the given players""" @@ -101,10 +106,17 @@ class RadiocolTest(helpers.SatTestCase): if isinstance(content, list): new_content = copy.deepcopy(content) for element in new_content: - if not element.hasAttribute('xmlns'): - element['xmlns'] = '' + if not element.hasAttribute("xmlns"): + element["xmlns"] = "" content = "".join([element.toXml() for element in new_content]) - return " <%s xmlns='%s'>%s%s> " % (to_jid.full(), type_, plugin.RADIOC_TAG, plugin.NC_RADIOCOL, content, plugin.RADIOC_TAG) + return "<%s xmlns='%s'>%s%s> " % ( + to_jid.full(), + type_, + plugin.RADIOC_TAG, + plugin.NC_RADIOCOL, + content, + plugin.RADIOC_TAG, + ) def _rejectSongCb(self, profile_index): """Check if the message "song_rejected" has been sent by the referee @@ -112,24 +124,41 @@ class RadiocolTest(helpers.SatTestCase): @param profile_index: uploader's profile""" sent = self.host.getSentMessage(0) content = "" - self.assertEqualXML(sent.toXml(), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + self.plugin_0045.getNick(0, profile_index), 'normal', content))) - self._roomGameCmd(sent, ['radiocolSongRejected', ROOM_JID.full(), 'Too many songs in queue']) + self.assertEqualXML( + sent.toXml(), + self._expectedMessage( + JID( + ROOM_JID.userhost() + + "/" + + self.plugin_0045.getNick(0, profile_index), + "normal", + content, + ) + ), + ) + self._roomGameCmd( + sent, ["radiocolSongRejected", ROOM_JID.full(), "Too many songs in queue"] + ) def _noUploadCb(self): """Check if the message "no_upload" has been sent by the referee and process the command with the profiles of each room users""" sent = self.host.getSentMessage(0) content = " " - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolNoUpload', ROOM_JID.full()]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolNoUpload", ROOM_JID.full()]) def _uploadOkCb(self): """Check if the message "upload_ok" has been sent by the referee and process the command with the profiles of each room users""" sent = self.host.getSentMessage(0) content = " " - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolUploadOk', ROOM_JID.full()]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolUploadOk", ROOM_JID.full()]) def _preloadCb(self, attrs, profile_index): """Check if the message "preload" has been sent by the referee @@ -138,15 +167,33 @@ class RadiocolTest(helpers.SatTestCase): @param profile_index: profile index of the uploader """ sent = self.host.getSentMessage(0) - attrs['sender'] = self.plugin_0045.getNick(0, profile_index) - radiocol_elt = domish.generateElementsNamed(sent.elements(), 'radiocol').next() - preload_elt = domish.generateElementsNamed(radiocol_elt.elements(), 'preload').next() - attrs['timestamp'] = preload_elt['timestamp'] # we could not guess it... - content = " " % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs]) - if sent.hasAttribute('from'): - del sent['from'] - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolPreload', ROOM_JID.full(), attrs['timestamp'], attrs['filename'], attrs['title'], attrs['artist'], attrs['album'], attrs['sender']]) + attrs["sender"] = self.plugin_0045.getNick(0, profile_index) + radiocol_elt = domish.generateElementsNamed(sent.elements(), "radiocol").next() + preload_elt = domish.generateElementsNamed( + radiocol_elt.elements(), "preload" + ).next() + attrs["timestamp"] = preload_elt["timestamp"] # we could not guess it... + content = " " % " ".join( + ["%s='%s'" % (attr, attrs[attr]) for attr in attrs] + ) + if sent.hasAttribute("from"): + del sent["from"] + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd( + sent, + [ + "radiocolPreload", + ROOM_JID.full(), + attrs["timestamp"], + attrs["filename"], + attrs["title"], + attrs["artist"], + attrs["album"], + attrs["sender"], + ], + ) def _playNextSongCb(self): """Check if the message "play" has been sent by the referee @@ -154,11 +201,13 @@ class RadiocolTest(helpers.SatTestCase): sent = self.host.getSentMessage(0) filename = self.playlist.pop(0) content = " " % filename - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content)) - self._roomGameCmd(sent, ['radiocolPlay', ROOM_JID.full(), filename]) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content) + ) + self._roomGameCmd(sent, ["radiocolPlay", ROOM_JID.full(), filename]) game_data = self.plugin.games[ROOM_JID] - if len(game_data['queue']) == plugin.QUEUE_LIMIT - 1: + if len(game_data["queue"]) == plugin.QUEUE_LIMIT - 1: self._uploadOkCb() def _addSongCb(self, d, filepath, profile_index): @@ -174,7 +223,7 @@ class RadiocolTest(helpers.SatTestCase): game_data = self.plugin.games[ROOM_JID] # this is copied from the plugin - if filepath.lower().endswith('.mp3'): + if filepath.lower().endswith(".mp3"): actual_song = MP3(filepath) try: song = EasyID3(filepath) @@ -182,27 +231,36 @@ class RadiocolTest(helpers.SatTestCase): class Info(object): def __init__(self, length): self.length = length + song.info = Info(actual_song.info.length) except ID3NoHeaderError: song = actual_song else: song = OggVorbis(filepath) - attrs = {'filename': os.path.basename(filepath), - 'title': song.get("title", ["Unknown"])[0], - 'artist': song.get("artist", ["Unknown"])[0], - 'album': song.get("album", ["Unknown"])[0], - 'length': str(song.info.length) - } - self.assertEqual(game_data['to_delete'][attrs['filename']], filepath) + attrs = { + "filename": os.path.basename(filepath), + "title": song.get("title", ["Unknown"])[0], + "artist": song.get("artist", ["Unknown"])[0], + "album": song.get("album", ["Unknown"])[0], + "length": str(song.info.length), + } + self.assertEqual(game_data["to_delete"][attrs["filename"]], filepath) - content = " " % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs]) + content = " " % " ".join( + ["%s='%s'" % (attr, attrs[attr]) for attr in attrs] + ) sent = self.host.getSentMessage(profile_index) - self.assertEqualXML(sent.toXml(), self._expectedMessage(REFEREE_FULL, 'normal', content)) + self.assertEqualXML( + sent.toXml(), self._expectedMessage(REFEREE_FULL, "normal", content) + ) - reject_song = len(game_data['queue']) >= plugin.QUEUE_LIMIT - no_upload = len(game_data['queue']) + 1 >= plugin.QUEUE_LIMIT - play_next = not game_data['playing'] and len(game_data['queue']) + 1 == plugin.QUEUE_TO_START + reject_song = len(game_data["queue"]) >= plugin.QUEUE_LIMIT + no_upload = len(game_data["queue"]) + 1 >= plugin.QUEUE_LIMIT + play_next = ( + not game_data["playing"] + and len(game_data["queue"]) + 1 == plugin.QUEUE_TO_START + ) self._roomGameCmd(sent, profile_index) # queue unchanged or +1 if reject_song: @@ -211,7 +269,7 @@ class RadiocolTest(helpers.SatTestCase): if no_upload: self._noUploadCb() self._preloadCb(attrs, profile_index) - self.playlist.append(attrs['filename']) + self.playlist.append(attrs["filename"]) if play_next: self._playNextSongCb() # queue -1 @@ -229,21 +287,28 @@ class RadiocolTest(helpers.SatTestCase): call = from_index from_index = 0 - sent['from'] = ROOM_JID.full() + '/' + self.plugin_0045.getNick(0, from_index) - recipient = JID(sent['to']).resource + sent["from"] = ROOM_JID.full() + "/" + self.plugin_0045.getNick(0, from_index) + recipient = JID(sent["to"]).resource # The message could have been sent to a room user (room_jid + '/' + nick), # but when it is received, the 'to' attribute of the message has been # changed to the recipient own JID. We need to simulate that here. if recipient: room = self.plugin_0045.getRoom(0, 0) - sent['to'] = Const.JID_STR[0] if recipient == room.nick else room.roster[recipient].entity.full() + sent["to"] = ( + Const.JID_STR[0] + if recipient == room.nick + else room.roster[recipient].entity.full() + ) for index in xrange(0, len(Const.PROFILE)): nick = self.plugin_0045.getNick(0, index) if nick: if not recipient or nick == recipient: - if call and (self.plugin.isPlayer(ROOM_JID, nick) or call[0] == 'radiocolStarted'): + if call and ( + self.plugin.isPlayer(ROOM_JID, nick) + or call[0] == "radiocolStarted" + ): args = copy.deepcopy(call) args.append(Const.PROFILE[index]) self.host.bridge.expectCall(*args) @@ -255,16 +320,35 @@ class RadiocolTest(helpers.SatTestCase): @param profile_index: index of the profile to be synchronized """ for nick in sync_data: - expected = self._expectedMessage(JID(ROOM_JID.userhost() + '/' + nick), 'normal', sync_data[nick]) + expected = self._expectedMessage( + JID(ROOM_JID.userhost() + "/" + nick), "normal", sync_data[nick] + ) sent = self.host.getSentMessage(0) self.assertEqualXML(sent.toXml(), expected) for elt in sync_data[nick]: - if elt.name == 'preload': - self.host.bridge.expectCall('radiocolPreload', ROOM_JID.full(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], Const.PROFILE[profile_index]) - elif elt.name == 'play': - self.host.bridge.expectCall('radiocolPlay', ROOM_JID.full(), elt['filename'], Const.PROFILE[profile_index]) - elif elt.name == 'no_upload': - self.host.bridge.expectCall('radiocolNoUpload', ROOM_JID.full(), Const.PROFILE[profile_index]) + if elt.name == "preload": + self.host.bridge.expectCall( + "radiocolPreload", + ROOM_JID.full(), + elt["timestamp"], + elt["filename"], + elt["title"], + elt["artist"], + elt["album"], + elt["sender"], + Const.PROFILE[profile_index], + ) + elif elt.name == "play": + self.host.bridge.expectCall( + "radiocolPlay", + ROOM_JID.full(), + elt["filename"], + Const.PROFILE[profile_index], + ) + elif elt.name == "no_upload": + self.host.bridge.expectCall( + "radiocolNoUpload", ROOM_JID.full(), Const.PROFILE[profile_index] + ) sync_data[nick] self._roomGameCmd(sent, []) @@ -280,12 +364,12 @@ class RadiocolTest(helpers.SatTestCase): if player_index not in PLAYERS_INDICES: # this user is actually not a player self.assertFalse(self.plugin.isPlayer(ROOM_JID, user_nick)) - to_jid, type_ = (JID(ROOM_JID.userhost() + '/' + user_nick), 'normal') + to_jid, type_ = (JID(ROOM_JID.userhost() + "/" + user_nick), "normal") else: # this user is a player self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) nicks.append(user_nick) - to_jid, type_ = (ROOM_JID, 'groupchat') + to_jid, type_ = (ROOM_JID, "groupchat") # Check that the message "players" has been sent by the referee expected = self._expectedMessage(to_jid, type_, self._buildPlayers(nicks)) @@ -293,7 +377,16 @@ class RadiocolTest(helpers.SatTestCase): self.assertEqualXML(sent.toXml(), expected) # Process the command with the profiles of each room users - self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]]) + self._roomGameCmd( + sent, + [ + "radiocolStarted", + ROOM_JID.full(), + REFEREE_FULL.full(), + nicks, + [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT], + ], + ) if sync: self._syncCb(self.plugin._getSyncData(ROOM_JID, [user_nick]), player_index) @@ -322,12 +415,14 @@ class RadiocolTest(helpers.SatTestCase): else: song_index = song_index % len(self.songs) src_filename = self.songs[song_index] - dst_filepath = '/tmp/%s%s' % (uuid.uuid1(), os.path.splitext(src_filename)[1]) + dst_filepath = "/tmp/%s%s" % (uuid.uuid1(), os.path.splitext(src_filename)[1]) shutil.copy(self.sound_dir + src_filename, dst_filepath) expect_io_error = False try: - d = self.plugin.radiocolSongAdded(REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index]) + d = self.plugin.radiocolSongAdded( + REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index] + ) except IOError: self.assertTrue(expect_io_error) return @@ -340,7 +435,7 @@ class RadiocolTest(helpers.SatTestCase): self.fail("Adding a song which is not OGG nor MP3 should fail!") self.assertEqual(failure.value.__class__, exceptions.DataError) - if src_filename.endswith('.ogg') or src_filename.endswith('.mp3'): + if src_filename.endswith(".ogg") or src_filename.endswith(".mp3"): d.addCallbacks(cb, cb) else: d.addCallbacks(eb, eb) @@ -362,14 +457,28 @@ class RadiocolTest(helpers.SatTestCase): nicks = [self.plugin_0045.getNick(0, 0)] sent = self.host.getSentMessage(0) - self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', self._buildPlayers(nicks))) - self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]]) + self.assertEqualXML( + sent.toXml(), + self._expectedMessage(ROOM_JID, "groupchat", self._buildPlayers(nicks)), + ) + self._roomGameCmd( + sent, + [ + "radiocolStarted", + ROOM_JID.full(), + REFEREE_FULL.full(), + nicks, + [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT], + ], + ) self._joinRoom(room, nicks, 1) # player joins self._joinRoom(room, nicks, 4) # user not playing joins song_index = 0 - self._uploadSong(song_index, 0) # ogg or mp3 file should exist in sat_media/test/song + self._uploadSong( + song_index, 0 + ) # ogg or mp3 file should exist in sat_media/test/song self._uploadSong(None, 0) # non existing file # another songs are added by Const.JID[1] until the radio starts + 1 to fill the queue @@ -379,7 +488,9 @@ class RadiocolTest(helpers.SatTestCase): self.plugin.playNext(Const.MUC[0], PROFILE) # simulate the end of the first song self._playNextSongCb() - self._uploadSong(song_index, 1) # now the song is accepted and the queue is full again + self._uploadSong( + song_index, 1 + ) # now the song is accepted and the queue is full again self._joinRoom(room, nicks, 3) # new player joins @@ -396,7 +507,7 @@ class RadiocolTest(helpers.SatTestCase): self._playNextSongCb() for filename in self.playlist: - self.plugin.deleteFile('/tmp/' + filename) + self.plugin.deleteFile("/tmp/" + filename) return defer.succeed(None) diff --git a/sat/test/test_plugin_misc_room_game.py b/sat/test/test_plugin_misc_room_game.py index a491edd4..a67269a4 100644 --- a/sat/test/test_plugin_misc_room_game.py +++ b/sat/test/test_plugin_misc_room_game.py @@ -30,8 +30,8 @@ from wokkel.muc import User from logging import WARNING # Data used for test initialization -NAMESERVICE = 'http://www.goffi.org/protocol/dummy' -TAG = 'dummy' +NAMESERVICE = "http://www.goffi.org/protocol/dummy" +TAG = "dummy" PLUGIN_INFO = { "name": "Dummy plugin", "import_name": "DUMMY", @@ -40,7 +40,7 @@ PLUGIN_INFO = { "dependencies": [], "main": "Dummy", "handler": "no", # handler MUST be "no" (dynamic inheritance) - "description": _("""Dummy plugin to test room game""") + "description": _("""Dummy plugin to test room game"""), } ROOM_JID = JID(Const.MUC_STR[0]) @@ -49,16 +49,21 @@ OTHER_PROFILE = Const.PROFILE[1] class RoomGameTest(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() def reinit(self, game_init={}, player_init={}): self.host.reinit() self.plugin = plugin.RoomGame(self.host) - self.plugin._init_(self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init) - self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host) - self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host) + self.plugin._init_( + self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init + ) + self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045( + self.host + ) + self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249( + self.host + ) for profile in Const.PROFILE: self.host.getClient(profile) # init self.host.profiles[profile] @@ -75,7 +80,13 @@ class RoomGameTest(helpers.SatTestCase): for i in xrange(0, len(players)): content += " %s " % (i, players[i]) content += "%s>" % tag - return "<%s xmlns='%s'>%s " % (to.full(), type_, TAG, NAMESERVICE, content) + return "<%s xmlns='%s'>%s " % ( + to.full(), + type_, + TAG, + NAMESERVICE, + content, + ) def test_createOrInvite_solo(self): self.reinit() @@ -87,14 +98,18 @@ class RoomGameTest(helpers.SatTestCase): self.reinit() self.plugin_0045.joinRoom(0, 0) other_players = [Const.JID[1], Const.JID[2]] - self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]) + self.plugin._createOrInvite( + self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0] + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) def test_createOrInvite_multi_waiting(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) self.plugin_0045.joinRoom(0, 0) other_players = [Const.JID[1], Const.JID[2]] - self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]) + self.plugin._createOrInvite( + self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0] + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, False)) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) @@ -102,11 +117,13 @@ class RoomGameTest(helpers.SatTestCase): self.reinit() self.initGame(0, 0) self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user)) - self.assertEqual([], self.plugin.games[ROOM_JID]['players']) + self.assertEqual([], self.plugin.games[ROOM_JID]["players"]) def test_checkJoinAuth(self): self.reinit() - check = lambda value: getattr(self, "assert%s" % value)(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)) + check = lambda value: getattr(self, "assert%s" % value)( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user) + ) check(False) # to test the "invited" mode, the referee must be different than the user to test self.initGame(0, 1) @@ -118,41 +135,66 @@ class RoomGameTest(helpers.SatTestCase): check(True) self.plugin.join_mode = self.plugin.NONE check(False) - self.plugin.games[ROOM_JID]['players'].append(Const.JID[0].user) + self.plugin.games[ROOM_JID]["players"].append(Const.JID[0].user) check(True) def test_updatePlayers(self): self.reinit() self.initGame(0, 0) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], []) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], []) self.plugin._updatePlayers(ROOM_JID, [], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], []) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], []) self.plugin._updatePlayers(ROOM_JID, ["user1"], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1"]) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], ["user1"]) self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0]) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"]) - self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0]) # should not be stored twice - self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"]) + self.assertEqual( + self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"] + ) + self.plugin._updatePlayers( + ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0] + ) # should not be stored twice + self.assertEqual( + self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"] + ) def test_synchronizeRoom(self): self.reinit() self.initGame(0, 0) self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", [])) - self.plugin.games[ROOM_JID]['players'].append("test1") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", []), + ) + self.plugin.games[ROOM_JID]["players"].append("test1") self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"])) - self.plugin.games[ROOM_JID]['started'] = True - self.plugin.games[ROOM_JID]['players'].append("test2") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"]), + ) + self.plugin.games[ROOM_JID]["started"] = True + self.plugin.games[ROOM_JID]["players"].append("test2") self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0]) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"])) - self.plugin.games[ROOM_JID]['players'].append("test3") - self.plugin.games[ROOM_JID]['players'].append("test4") + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"]), + ) + self.plugin.games[ROOM_JID]["players"].append("test3") + self.plugin.games[ROOM_JID]["players"].append("test4") user1 = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user) user2 = JID(ROOM_JID.userhost() + "/" + Const.JID[1].user) self.plugin._synchronizeRoom(ROOM_JID, [user1, user2], Const.PROFILE[0]) - self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user1, "normal", "started", ["test1", "test2", "test3", "test4"])) - self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user2, "normal", "started", ["test1", "test2", "test3", "test4"])) + self.assertEqualXML( + self.host.getSentMessageXml(0), + self._expectedMessage( + user1, "normal", "started", ["test1", "test2", "test3", "test4"] + ), + ) + self.assertEqualXML( + self.host.getSentMessageXml(0), + self._expectedMessage( + user2, "normal", "started", ["test1", "test2", "test3", "test4"] + ), + ) def test_invitePlayers(self): self.reinit() @@ -160,25 +202,41 @@ class RoomGameTest(helpers.SatTestCase): self.plugin_0045.joinRoom(0, 1) self.assertEqual(self.plugin.invitations[ROOM_JID], []) room = self.plugin_0045.getRoom(0, 0) - nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0]) - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()]) + nicks = self.plugin._invitePlayers( + room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0] + ) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()], + ) # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost self.assertEqual(nicks, [Const.JID[1].user, Const.JID[2].user]) - nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0]) - self.assertEqual(self.plugin.invitations[ROOM_JID][1][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()]) + nicks = self.plugin._invitePlayers( + room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0] + ) + self.assertEqual( + self.plugin.invitations[ROOM_JID][1][1], + [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()], + ) # this time Const.JID[1] and Const.JID[3] have the same user but the host differs self.assertEqual(nicks, [Const.JID[1].user]) def test_checkInviteAuth(self): - def check(value, index): nick = self.plugin_0045.getNick(0, index) - getattr(self, "assert%s" % value)(self.plugin._checkInviteAuth(ROOM_JID, nick)) + getattr(self, "assert%s" % value)( + self.plugin._checkInviteAuth(ROOM_JID, nick) + ) self.reinit() - for mode in [self.plugin.FROM_ALL, self.plugin.FROM_NONE, self.plugin.FROM_REFEREE, self.plugin.FROM_PLAYERS]: + for mode in [ + self.plugin.FROM_ALL, + self.plugin.FROM_NONE, + self.plugin.FROM_REFEREE, + self.plugin.FROM_PLAYERS, + ]: self.plugin.invite_mode = mode check(True, 0) @@ -193,7 +251,7 @@ class RoomGameTest(helpers.SatTestCase): check(True, 0) check(False, 1) user_nick = self.plugin_0045.joinRoom(0, 1) - self.plugin.games[ROOM_JID]['players'].append(user_nick) + self.plugin.games[ROOM_JID]["players"].append(user_nick) self.plugin.invite_mode = self.plugin.FROM_PLAYERS check(True, 0) check(True, 1) @@ -210,22 +268,25 @@ class RoomGameTest(helpers.SatTestCase): self.initGame(0, 0) self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 0))) user_nick = self.plugin_0045.joinRoom(0, 1) - self.plugin.games[ROOM_JID]['players'].append(user_nick) + self.plugin.games[ROOM_JID]["players"].append(user_nick) self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) self.assertFalse(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 2))) def test_checkWaitAuth(self): - def check(value, other_players, confirmed, rest): room = self.plugin_0045.getRoom(0, 0) - self.assertEqual((value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players) + ) self.reinit() self.initGame(0, 0) other_players = [Const.JID[1], Const.JID[3]] self.plugin.wait_mode = self.plugin.FOR_NONE check(True, [], [], []) - check(True, [Const.JID[0]], [], [Const.JID[0]]) # getRoomNickOfUser checks for the other users only + check( + True, [Const.JID[0]], [], [Const.JID[0]] + ) # getRoomNickOfUser checks for the other users only check(True, other_players, [], other_players) self.plugin.wait_mode = self.plugin.FOR_ALL check(True, [], [], []) @@ -234,28 +295,51 @@ class RoomGameTest(helpers.SatTestCase): self.plugin_0045.joinRoom(0, 1) check(False, other_players, [], other_players) self.plugin_0045.joinRoom(0, 4) - check(False, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0)], [Const.JID[3]]) + check( + False, + other_players, + [self.plugin_0045.getNickOfUser(0, 1, 0)], + [Const.JID[3]], + ) self.plugin_0045.joinRoom(0, 3) - check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0), - self.plugin_0045.getNickOfUser(0, 3, 0)], []) + check( + True, + other_players, + [ + self.plugin_0045.getNickOfUser(0, 1, 0), + self.plugin_0045.getNickOfUser(0, 3, 0), + ], + [], + ) other_players = [Const.JID[1], Const.JID[3], Const.JID[2]] # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost - check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0), - self.plugin_0045.getNickOfUser(0, 3, 0), - self.plugin_0045.getNickOfUser(0, 2, 0)], []) + check( + True, + other_players, + [ + self.plugin_0045.getNickOfUser(0, 1, 0), + self.plugin_0045.getNickOfUser(0, 3, 0), + self.plugin_0045.getNickOfUser(0, 2, 0), + ], + [], + ) def test_prepareRoom_trivial(self): self.reinit() other_players = [] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user) + ) self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[0].user)) self.assertEqual((True, [], []), self.plugin._checkWaitAuth(ROOM_JID, [])) self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[0].user)) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) def test_prepareRoom_invite(self): self.reinit() @@ -264,10 +348,16 @@ class RoomGameTest(helpers.SatTestCase): room = self.plugin_0045.getRoom(0, 0) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)) - self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user) + ) + self.assertFalse( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user) + ) self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user)) - self.assertEqual((True, [], other_players), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (True, [], other_players), self.plugin._checkWaitAuth(room, other_players) + ) player2_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[player2_nick], PROFILE) @@ -275,21 +365,32 @@ class RoomGameTest(helpers.SatTestCase): self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, player2_nick)) self.assertFalse(self.plugin.isReferee(ROOM_JID, player2_nick)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, player2_nick)) - self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))) + self.assertTrue( + self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)) + ) self.assertFalse(self.plugin.isPlayer(ROOM_JID, "xxx")) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1])) + self.assertEqual( + (False, False), + self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1]), + ) def test_prepareRoom_score1(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) other_players = [Const.JID[1], Const.JID[2]] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) - self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)) - self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)) + self.assertTrue( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user) + ) + self.assertFalse( + self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user) + ) self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user)) - self.assertEqual((False, [], other_players), self.plugin._checkWaitAuth(room, other_players)) + self.assertEqual( + (False, [], other_players), self.plugin._checkWaitAuth(room, other_players) + ) user_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) @@ -298,25 +399,33 @@ class RoomGameTest(helpers.SatTestCase): self.assertFalse(self.plugin.isReferee(ROOM_JID, user_nick)) self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick)) # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost - self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))) + self.assertTrue( + self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)) + ) # the following assertion is True because Const.JID[1] nick in the room is equal to Const.JID[3].user self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[3].user)) # but Const.JID[3] is actually not in the room self.assertEqual(self.plugin_0045.getNickOfUser(0, 3, 0), None) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0])) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0]) + ) def test_prepareRoom_score2(self): - self.reinit(player_init={'score': 0}) + self.reinit(player_init={"score": 0}) other_players = [Const.JID[1], Const.JID[4]] self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) user_nick = self.plugin_0045.joinRoom(0, 1) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) def test_userJoinedTrigger(self): self.reinit(player_init={"xxx": "xyz"}) @@ -324,34 +433,50 @@ class RoomGameTest(helpers.SatTestCase): self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) nicks = [self.plugin_0045.getNick(0, 0)] - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", nicks), + ) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 1) # wrong profile user_nick = self.plugin_0045.joinRoom(0, 1) room = self.plugin_0045.getRoom(0, 1) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), OTHER_PROFILE) - self.assertEqual(self.host.getSentMessage(0), None) # no new message has been sent + self.assertEqual( + self.host.getSentMessage(0), None + ) # no new message has been sent self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is allowed, wait for one more room = self.plugin_0045.getRoom(0, 0) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "players", nicks), + ) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is not allowed user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + user_nick), "normal", "players", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage( + JID(ROOM_JID.userhost() + "/" + user_nick), "normal", "players", nicks + ), + ) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) # game not started # referee profile, user is allowed, everybody here user_nick = self.plugin_0045.joinRoom(0, 3) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) # game started self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) @@ -364,7 +489,10 @@ class RoomGameTest(helpers.SatTestCase): user_nick = self.plugin_0045.joinRoom(0, 3) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) def test_userLeftTrigger(self): @@ -373,7 +501,14 @@ class RoomGameTest(helpers.SatTestCase): self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE) room = self.plugin_0045.getRoom(0, 0) nicks = [self.plugin_0045.getNick(0, 0)] - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID(), Const.JID[4].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [ + Const.JID[1].userhostJID(), + Const.JID[3].userhostJID(), + Const.JID[4].userhostJID(), + ], + ) # one user joins user_nick = self.plugin_0045.joinRoom(0, 1) @@ -381,16 +516,20 @@ class RoomGameTest(helpers.SatTestCase): nicks.append(user_nick) # the user leaves - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) room = self.plugin_0045.getRoom(0, 1) # to not call self.plugin_0045.leaveRoom(0, 1) here, we are testing the trigger with a wrong profile - self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), Const.PROFILE[1]) # not the referee - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.plugin.userLeftTrigger( + room, User(user_nick, Const.JID[1]), Const.PROFILE[1] + ) # not the referee + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) room = self.plugin_0045.getRoom(0, 0) user_nick = self.plugin_0045.leaveRoom(0, 1) - self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), PROFILE) # referee + self.plugin.userLeftTrigger( + room, User(user_nick, Const.JID[1]), PROFILE + ) # referee nicks.pop() - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) # all the users join user_nick = self.plugin_0045.joinRoom(0, 1) @@ -402,20 +541,25 @@ class RoomGameTest(helpers.SatTestCase): user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) # one user leaves user_nick = self.plugin_0045.leaveRoom(0, 4) self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.pop() - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()] + ) # another leaves user_nick = self.plugin_0045.leaveRoom(0, 3) self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[3]), PROFILE) nicks.pop() - self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()]) + self.assertEqual( + self.plugin.invitations[ROOM_JID][0][1], + [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()], + ) # they can join again user_nick = self.plugin_0045.joinRoom(0, 3) @@ -424,31 +568,43 @@ class RoomGameTest(helpers.SatTestCase): user_nick = self.plugin_0045.joinRoom(0, 4) self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE) nicks.append(user_nick) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0) def test__checkCreateGameAndInit(self): self.reinit() helpers.muteLogging() - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) helpers.unmuteLogging() nick = self.plugin_0045.joinRoom(0, 0) - self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) + self.assertEqual( + (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) self.assertTrue(self.plugin._gameExists(ROOM_JID, False)) self.assertFalse(self.plugin._gameExists(ROOM_JID, True)) self.assertTrue(self.plugin.isReferee(ROOM_JID, nick)) helpers.muteLogging() - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) helpers.unmuteLogging() self.plugin_0045.joinRoom(0, 1) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) self.plugin.createGame(ROOM_JID, [Const.JID[1]], PROFILE) - self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)) - self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)) + self.assertEqual( + (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE) + ) + self.assertEqual( + (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE) + ) def test_createGame(self): @@ -460,27 +616,39 @@ class RoomGameTest(helpers.SatTestCase): # game not exists self.plugin.createGame(ROOM_JID, nicks, PROFILE) self.assertTrue(self.plugin._gameExists(ROOM_JID, True)) - self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) for nick in nicks: - self.assertEqual('init', self.plugin.games[ROOM_JID]['status'][nick]) - self.assertEqual(self.plugin.player_init, self.plugin.games[ROOM_JID]['players_data'][nick]) - self.plugin.games[ROOM_JID]['players_data'][nick]["xxx"] = nick + self.assertEqual("init", self.plugin.games[ROOM_JID]["status"][nick]) + self.assertEqual( + self.plugin.player_init, self.plugin.games[ROOM_JID]["players_data"][nick] + ) + self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"] = nick for nick in nicks: # checks that a copy of self.player_init has been done and not a reference - self.assertEqual(nick, self.plugin.games[ROOM_JID]['players_data'][nick]['xxx']) + self.assertEqual( + nick, self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"] + ) # game exists, current profile is referee self.reinit(player_init={"xxx": "xyz"}) self.initGame(0, 0) - self.plugin.games[ROOM_JID]['started'] = True + self.plugin.games[ROOM_JID]["started"] = True self.plugin.createGame(ROOM_JID, nicks, PROFILE) - self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks)) + self.assertEqual( + self.host.getSentMessageXml(0), + self._expectedMessage(ROOM_JID, "groupchat", "started", nicks), + ) # game exists, current profile is not referee self.reinit(player_init={"xxx": "xyz"}) self.initGame(0, 0) - self.plugin.games[ROOM_JID]['started'] = True + self.plugin.games[ROOM_JID]["started"] = True self.plugin_0045.joinRoom(0, 1) self.plugin.createGame(ROOM_JID, nicks, OTHER_PROFILE) - self.assertEqual(self.host.getSentMessage(0), None) # no sync message has been sent by other_profile + self.assertEqual( + self.host.getSentMessage(0), None + ) # no sync message has been sent by other_profile diff --git a/sat/test/test_plugin_misc_text_syntaxes.py b/sat/test/test_plugin_misc_text_syntaxes.py index 9f4477d2..f59d7553 100644 --- a/sat/test/test_plugin_misc_text_syntaxes.py +++ b/sat/test/test_plugin_misc_text_syntaxes.py @@ -91,10 +91,14 @@ class SanitisationTest(helpers.SatTestCase): def test_html2text(self): """Check that html2text is not inserting \n in the middle of that link. By default lines are truncated after the 79th characters.""" - source = "" + source = '
' expected = "" try: - d = self.text_syntaxes.convert(source, self.text_syntaxes.SYNTAX_XHTML, self.text_syntaxes.SYNTAX_MARKDOWN) + d = self.text_syntaxes.convert( + source, + self.text_syntaxes.SYNTAX_XHTML, + self.text_syntaxes.SYNTAX_MARKDOWN, + ) except plugin_misc_text_syntaxes.UnknownSyntax: raise SkipTest("Markdown syntax is not available.") d.addCallback(self.assertEqual, expected) @@ -108,4 +112,3 @@ class SanitisationTest(helpers.SatTestCase): expected = u"""test retest toto""" result = self.text_syntaxes._removeMarkups(self.EVIL_HTML2) self.assertEqual(re.sub(r"\s+", " ", result).rstrip(), expected.rstrip()) - diff --git a/sat/test/test_plugin_xep_0033.py b/sat/test/test_plugin_xep_0033.py index 88f609e2..4124c9d3 100644 --- a/sat/test/test_plugin_xep_0033.py +++ b/sat/test/test_plugin_xep_0033.py @@ -36,11 +36,10 @@ JID_STR_X_TO = Const.JID_STR[0] JID_STR_X_CC = Const.JID_STR[1] JID_STR_X_BCC = Const.JID_STR[2] -ADDRS = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC) +ADDRS = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC) class XEP_0033Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = plugin.XEP_0033(self.host) @@ -56,48 +55,67 @@ class XEP_0033Test(helpers.SatTestCase):
content - """ % (JID_STR_FROM, JID_STR_TO) - mess_data['xml'] = parseXml(original_stanza.encode("utf-8")) + """ % ( + JID_STR_FROM, + JID_STR_TO, + ) + mess_data["xml"] = parseXml(original_stanza.encode("utf-8")) return mess_data def _assertAddresses(self, mess_data): """The mess_data that we got here has been modified by self.plugin.messageSendTrigger, check that the addresses element has been added to the stanza.""" - expected = self._get_mess_data()['xml'] - addresses_extra = """ + expected = self._get_mess_data()["xml"] + addresses_extra = ( + """- """ % ADDRS - addresses_element = parseXml(addresses_extra.encode('utf-8')) + """ + % ADDRS + ) + addresses_element = parseXml(addresses_extra.encode("utf-8")) expected.addChild(addresses_element) - self.assertEqualXML(mess_data['xml'].toXml().encode("utf-8"), expected.toXml().encode("utf-8")) + self.assertEqualXML( + mess_data["xml"].toXml().encode("utf-8"), expected.toXml().encode("utf-8") + ) def _checkSentAndStored(self): """Check that all the recipients got their messages and that the history has been filled. @@ -108,7 +126,9 @@ class XEP_0033Test(helpers.SatTestCase): def cb(entities, to_jid): if host in entities: - if host not in sent: # send the message to the entity offering the feature + if ( + host not in sent + ): # send the message to the entity offering the feature sent.append(host) stored.append(host) stored.append(to_jid) # store in history for each recipient @@ -127,7 +147,9 @@ class XEP_0033Test(helpers.SatTestCase): def cb_list(dummy): msg = "/!\ see the comments in XEP_0033.sendAndStoreMessage" - sent_recipients = [JID(elt['to']) for elt in self.host.getSentMessages(PROFILE_INDEX)] + sent_recipients = [ + JID(elt["to"]) for elt in self.host.getSentMessages(PROFILE_INDEX) + ] self.assertEqualUnsortedList(sent_recipients, sent, msg) self.assertEqualUnsortedList(self.host.stored_messages, stored, msg) @@ -143,10 +165,14 @@ class XEP_0033Test(helpers.SatTestCase): pre_treatments = defer.Deferred() post_treatments = defer.Deferred() helpers.muteLogging() - self.plugin.messageSendTrigger(self.host.getClient[PROFILE], data, pre_treatments, post_treatments) + self.plugin.messageSendTrigger( + self.host.getClient[PROFILE], data, pre_treatments, post_treatments + ) post_treatments.callback(data) helpers.unmuteLogging() - post_treatments.addCallbacks(self._assertAddresses, lambda failure: failure.trap(CancelError)) + post_treatments.addCallbacks( + self._assertAddresses, lambda failure: failure.trap(CancelError) + ) return post_treatments def test_messageSendTriggerFeatureNotSupported(self): diff --git a/sat/test/test_plugin_xep_0085.py b/sat/test/test_plugin_xep_0085.py index 02d8a408..cc9f7f9d 100644 --- a/sat/test/test_plugin_xep_0085.py +++ b/sat/test/test_plugin_xep_0085.py @@ -30,11 +30,16 @@ from wokkel.generic import parseXml class XEP_0085Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = plugin.XEP_0085(self.host) - self.host.memory.setParam(plugin.PARAM_NAME, True, plugin.PARAM_KEY, C.NO_SECURITY_LIMIT, Const.PROFILE[0]) + self.host.memory.setParam( + plugin.PARAM_NAME, + True, + plugin.PARAM_KEY, + C.NO_SECURITY_LIMIT, + Const.PROFILE[0], + ) def test_messageReceived(self): for state in plugin.CHAT_STATES: @@ -43,37 +48,51 @@ class XEP_0085Test(helpers.SatTestCase): %s <%s xmlns='%s'/>%s - """ % (Const.JID_STR[1], Const.JID_STR[0], - ("%s" % mess_data['message']) if state == "active" else "") - mess_data['xml'] = parseXml(stanza.encode("utf-8")) - expected = deepcopy(mess_data['xml']) + """ % ( + Const.JID_STR[1], + Const.JID_STR[0], + ("%s" % mess_data["message"]) if state == "active" else "", + ) + mess_data["xml"] = parseXml(stanza.encode("utf-8")) + expected = deepcopy(mess_data["xml"]) expected.addElement(state, plugin.NS_CHAT_STATES) post_treatments = defer.Deferred() - self.plugin.messageSendTrigger(self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments) + self.plugin.messageSendTrigger( + self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments + ) post_treatments.addCallback(cb) post_treatments.callback(mess_data) diff --git a/sat/test/test_plugin_xep_0203.py b/sat/test/test_plugin_xep_0203.py index bd47e09a..74eb78bc 100644 --- a/sat/test/test_plugin_xep_0203.py +++ b/sat/test/test_plugin_xep_0203.py @@ -27,11 +27,10 @@ from twisted.words.protocols.jabber.jid import JID from dateutil.tz import tzutc import datetime -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" class XEP_0203Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0203(self.host) @@ -44,7 +43,8 @@ class XEP_0203Test(helpers.SatTestCase): Offline Storage """ - message_xml = """ + message_xml = ( + """text %s - """ % delay_xml + """ + % delay_xml + ) - parent = domish.Element((None, 'message')) - parent['from'] = 'romeo@montague.net/orchard' - parent['to'] = 'juliet@capulet.com' - parent['type'] = 'chat' - parent.addElement('body', None, 'text') + parent = domish.Element((None, "message")) + parent["from"] = "romeo@montague.net/orchard" + parent["to"] = "juliet@capulet.com" + parent["type"] = "chat" + parent.addElement("body", None, "text") stamp = datetime.datetime(2002, 9, 10, 23, 8, 25, tzinfo=tzutc()) - elt = self.plugin.delay(stamp, JID('capulet.com'), 'Offline Storage', parent) + elt = self.plugin.delay(stamp, JID("capulet.com"), "Offline Storage", parent) self.assertEqualXML(elt.toXml(), delay_xml, True) self.assertEqualXML(parent.toXml(), message_xml, True) diff --git a/sat/test/test_plugin_xep_0277.py b/sat/test/test_plugin_xep_0277.py index 85f4420a..fc490ee0 100644 --- a/sat/test/test_plugin_xep_0277.py +++ b/sat/test/test_plugin_xep_0277.py @@ -29,7 +29,8 @@ from wokkel.pubsub import NS_PUBSUB class XEP_0277Test(helpers.SatTestCase): - PUBSUB_ENTRY_1 = u""" + PUBSUB_ENTRY_1 = ( + u"""- - """ % plugin_xep_0277.NS_ATOM + """ + % plugin_xep_0277.NS_ATOM + ) - PUBSUB_ENTRY_2 = u""" + PUBSUB_ENTRY_2 = ( + u"""
<span>titre</span> @@ -43,9 +44,12 @@ class XEP_0277Test(helpers.SatTestCase):- - """ % plugin_xep_0277.NS_ATOM + """ + % plugin_xep_0277.NS_ATOM + ) def setUp(self): self.host = helpers.FakeSAT() @@ -72,39 +78,48 @@ class XEP_0277Test(helpers.SatTestCase): def addPEPEvent(self, *args): pass + self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host) self.host.plugins["XEP-0163"] = XEP_0163(self.host) reload(plugin_misc_text_syntaxes) # reload the plugin to avoid conflict error - self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes(self.host) + self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes( + self.host + ) self.plugin = plugin_xep_0277.XEP_0277(self.host) def test_item2mbdata_1(self): - expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'title': u'titre', - u'updated': u'1392992199.0', - u'published': u'1392992198.0', - u'content': u'
<div>titre</div> @@ -61,7 +65,9 @@ class XEP_0277Test(helpers.SatTestCase):contenu
texte sans baliseautre contenu
', - u'content_xhtml': u'', - u'author': u'test1@souliane.org' - } - item_elt = ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next() + expected = { + u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"title": u"titre", + u"updated": u"1392992199.0", + u"published": u"1392992198.0", + u"content": u"contenu
texte sans baliseautre contenu
contenu
texte sans baliseautre contenu
", + u"content_xhtml": u"", + u"author": u"test1@souliane.org", + } + item_elt = ( + ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next() + ) d = self.plugin.item2mbdata(item_elt) d.addCallback(self.assertEqual, expected) return d def test_item2mbdata_2(self): - expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51', - u'title': u'contenu
texte sans baliseautre contenu
titre', - u'title_xhtml': u'', - u'updated': u'1392992199.0', - u'published': u'1392992198.0', - u'content': u'titre', - u'content_xhtml': u'contenu
texte dans baliseautre contenu
', - u'author': u'test1@souliane.org' - } - item_elt = ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next() + expected = { + u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51", + u"title": u"contenu
texte dans baliseautre contenu
titre", + u"title_xhtml": u'', + u"updated": u"1392992199.0", + u"published": u"1392992198.0", + u"content": u"titre", + u"content_xhtml": u"contenu
texte dans baliseautre contenu
", + u"author": u"test1@souliane.org", + } + item_elt = ( + ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next() + ) d = self.plugin.item2mbdata(item_elt) d.addCallback(self.assertEqual, expected) return d diff --git a/sat/test/test_plugin_xep_0297.py b/sat/test/test_plugin_xep_0297.py index 840da891..9fe4f14d 100644 --- a/sat/test/test_plugin_xep_0297.py +++ b/sat/test/test_plugin_xep_0297.py @@ -30,18 +30,18 @@ import datetime from wokkel.generic import parseXml -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" class XEP_0297Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0297(self.host) - self.host.plugins['XEP-0203'] = XEP_0203(self.host) + self.host.plugins["XEP-0203"] = XEP_0203(self.host) def test_delay(self): - stanza = parseXml(""" + stanza = parseXml( + """contenu
texte dans baliseautre contenu
A most courteous exposition! @@ -71,8 +74,16 @@ class XEP_0297Test(helpers.SatTestCase): """ stamp = datetime.datetime(2010, 7, 10, 23, 8, 25, tzinfo=tzutc()) - d = self.plugin.forward(stanza, JID('mercutio@verona.lit'), stamp, - body='A most courteous exposition!', - profile_key=C.PROFILE[0]) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), output, True)) + d = self.plugin.forward( + stanza, + JID("mercutio@verona.lit"), + stamp, + body="A most courteous exposition!", + profile_key=C.PROFILE[0], + ) + d.addCallback( + lambda dummy: self.assertEqualXML( + self.host.getSentMessageXml(0), output, True + ) + ) return d diff --git a/sat/test/test_plugin_xep_0313.py b/sat/test/test_plugin_xep_0313.py index 3e842615..d22947f7 100644 --- a/sat/test/test_plugin_xep_0313.py +++ b/sat/test/test_plugin_xep_0313.py @@ -33,13 +33,12 @@ import datetime from sat_tmp.wokkel.rsm import RSMRequest from sat_tmp.wokkel.mam import buildForm, MAMRequest -NS_PUBSUB = 'http://jabber.org/protocol/pubsub' -SERVICE = 'sat-pubsub.tazar.int' +NS_PUBSUB = "http://jabber.org/protocol/pubsub" +SERVICE = "sat-pubsub.tazar.int" SERVICE_JID = JID(SERVICE) class XEP_0313Test(helpers.SatTestCase): - def setUp(self): self.host = helpers.FakeSAT() self.plugin = XEP_0313(self.host) @@ -52,9 +51,14 @@ class XEP_0313Test(helpers.SatTestCase):- """ % (("H_%d" % domish.Element._idCounter), SERVICE) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) d = self.plugin.queryArchive(self.client, MAMRequest(), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchivePubsub(self): @@ -62,9 +66,16 @@ class XEP_0313Test(helpers.SatTestCase): - """ % (("H_%d" % domish.Element._idCounter), SERVICE) - d = self.plugin.queryArchive(self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID) - d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)) + """ % ( + ("H_%d" % domish.Element._idCounter), + SERVICE, + ) + d = self.plugin.queryArchive( + self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID + ) + d.addCallback( + lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True) + ) return d def test_queryArchiveWith(self): @@ -81,10 +92,15 @@ class XEP_0313Test(helpers.SatTestCase):