Source code for skpy.msg

import base64
import json
import re
from datetime import datetime, date
import time

from bs4 import BeautifulSoup, Tag

from .core import SkypeObj, SkypeEnum
from .util import SkypeUtils
from .conn import SkypeConnection


def makeTag(name, string=None, **kwargs):
    tag = Tag(name=name, attrs=kwargs)
    for key in kwargs:
        if kwargs[key] is None:
            kwargs[key] = ""
    if string:
        tag.string = string
    return tag


[docs]@SkypeUtils.initAttrs @SkypeUtils.convertIds("user", "chat") class SkypeMsg(SkypeObj): """ A message either sent or received in a conversation. An edit is represented by a follow-up message with the same :attr:`clientId`, which replaces the earlier message. Attributes: id (str): Identifier of the message provided by the server, usually a timestamp. type (str): Raw message type, as specified by the Skype API. time (datetime.datetime): Original arrival time of the message. clientId (str): Identifier generated by the client, used as a reference for edits. user (:class:`.SkypeUser`): User that sent the message. chat (:class:`.SkypeChat`): Conversation where this message was received. content (str): Raw message content, as received from the API. html (str): Recreated content string based on the field values. deleted (bool): Whether the message content was deleted by the sender. """
[docs] @staticmethod def bold(s): """ Format text to be bold. Args: s (str): string to format Returns: str: formatted string """ return """<b raw_pre="*" raw_post="*">{0}</b>""".format(s)
[docs] @staticmethod def italic(s): """ Format text to be italic. Args: s (str): string to format Returns: str: formatted string """ return """<i raw_pre="_" raw_post="_">{0}</i>""".format(s)
[docs] @staticmethod def strike(s): """ Format text to be struck through. Args: s (str): string to format Returns: str: formatted string """ return """<s raw_pre="~" raw_post="~">{0}</s>""".format(s)
[docs] @staticmethod def mono(s): """ Format text to be monospaced. Args: s (str): string to format Returns: str: formatted string """ return """<pre raw_pre="{{code}}" raw_post="{{code}}">{0}</pre>""".format(s)
[docs] @staticmethod def colour(s, colour): """ Format text to be coloured. Args: s (str): string to format colour (str): colour to display text in Returns: str: formatted string """ return """<font color="{0}">{1}</font>""".format(colour, s)
[docs] @staticmethod def emote(shortcut): """ Display an emoticon. This accepts any valid shortcut. Args: shortcut (str): emoticon shortcut Returns: str: tag to render the emoticon """ for emote in SkypeUtils.static["items"]: if shortcut == emote["id"]: return """<ss type="{0}">{1}</ss>""".format(shortcut, emote["shortcuts"][0]) elif shortcut in emote["shortcuts"]: return """<ss type="{0}">{1}</ss>""".format(emote["id"], shortcut) # No match, return the input as-is. return shortcut
[docs] @staticmethod def mention(user): """ Mention a user in a message. This may trigger a notification for them even if the conversation is muted. Args: user (SkypeUser): user who is to be mentioned Returns: str: tag to display the mention """ return """<at id="8:{0}">{1}</at>""".format(user.id, user.name)
mention_all = """<at id="*">all</at>""" """Mention all users of a group chat in a message."""
[docs] @staticmethod def quote(user, chat, timestamp, content): """ Display a message excerpt as a quote from another user. Skype for Web doesn't support native quotes, and instead displays the legacy quote text. Supported desktop clients show a blockquote with the author's name and timestamp underneath. .. note:: Anomalous API behaviour: it is possible to fake the message content of a quote. Args: user (SkypeUser): user who is to be quoted saying the message chat (SkypeChat): conversation the quote was originally seen in timestamp (datetime.datetime): original arrival time of the quoted message content (str): excerpt of the original message to be quoted Returns: str: tag to display the excerpt as a quote """ # Single conversations lose their prefix here. chatId = chat.id if chat.id.split(":")[0] == "19" else SkypeUtils.noPrefix(chat.id) # Legacy timestamp includes the date if the quote is not from today. unixTime = int(time.mktime(timestamp.timetuple())) legacyTime = timestamp.strftime("{0}%H:%M:%S".format("" if timestamp.date() == date.today() else "%d/%m/%Y ")) return """<quote author="{0}" authorname="{1}" conversation="{2}" timestamp="{3}"><legacyquote>""" \ """[{4}] {1}: </legacyquote>{5}<legacyquote>\n\n&lt;&lt;&lt; </legacyquote></quote>""" \ .format(user.id, user.name, chatId, unixTime, legacyTime, content)
[docs] @staticmethod def uriObject(content, type, url, thumb=None, title=None, desc=None, **values): """ Generate the markup needed for a URI component in a rich message. Args: content (str): object-specific content inside the object tag type (str): URI object type url (str): URL to content title (str): name of object desc (str): additional line of information thumb (str): URL to thumbnail of content values (dict): standard value tags of the form ``<key v="value"/>`` Returns: str: ``<URIObject>`` tag """ titleTag = """<Title>Title: {0}</Title>""".format(title) if title else """<Title/>""" descTag = """<Description>Description: {0}</Description>""".format(desc) if desc else """<Description/>""" thumbAttr = " url_thumbnail=\"{0}\"".format(thumb) if thumb else "" valTags = "".join("""<{0} v="{1}"/>""".format(k, v) for k, v in values.items()) return """<URIObject type="{1}" uri="{2}"{3}>{4}{5}{6}{0}</URIObject>""" \ .format(content, type, url, thumbAttr, titleTag, descTag, valTags)
attrs = ("id", "type", "time", "clientId", "userId", "chatId", "content")
[docs] @classmethod def rawToFields(cls, raw={}): try: msgTime = datetime.strptime(raw.get("originalarrivaltime", ""), "%Y-%m-%dT%H:%M:%S.%fZ") except ValueError: msgTime = datetime.now() fields = {"id": raw.get("id"), "type": raw.get("messagetype"), "time": msgTime, "clientId": raw.get("clientmessageid", raw.get("skypeeditedid")), "userId": SkypeUtils.userToId(raw.get("from", "")), "chatId": SkypeUtils.chatToId(raw.get("conversationLink", "")), "content": raw.get("content")} if fields["content"]: fields.update(cls.contentToFields(BeautifulSoup(fields["content"], "html.parser"))) return fields
@classmethod def contentToFields(cls, content): return {}
[docs] @classmethod def fromRaw(cls, skype=None, raw={}): msgCls = {"Text": SkypeTextMsg, "RichText": SkypeTextMsg, "RichText/Contacts": SkypeContactMsg, "RichText/Location": SkypeLocationMsg, "RichText/Media_GenericFile": SkypeFileMsg, "RichText/UriObject": SkypeImageMsg, "RichText/Media_AudioMsg": SkypeAudioMsg, "RichText/Media_Video": SkypeVideoMsg, "RichText/Media_Card": SkypeCardMsg, "Event/Call": SkypeCallMsg, "ThreadActivity/TopicUpdate": SkypeTopicPropertyMsg, "ThreadActivity/JoiningEnabledUpdate": SkypeOpenPropertyMsg, "ThreadActivity/HistoryDisclosedUpdate": SkypeHistoryPropertyMsg, "ThreadActivity/AddMember": SkypeAddMemberMsg, "ThreadActivity/RoleUpdate": SkypeChangeMemberMsg, "ThreadActivity/DeleteMember": SkypeRemoveMemberMsg}.get(raw.get("messagetype"), cls) return msgCls(skype, raw, **msgCls.rawToFields(raw))
@property def html(self): # If not overridden in a subclass, just return the existing content. return self.content @property def deleted(self): return self.content == ""
[docs] def read(self): """ Mark this message as read by sending an updated consumption horizon. """ self.chat.setConsumption("{0};{1};{0}".format(self.clientId, int(time.time() * 1000)))
[docs] def edit(self, content, me=False, rich=False): """ Send an edit of this message. Arguments are passed to :meth:`.SkypeChat.sendMsg`. .. note:: Anomalous API behaviour: messages can be undeleted by editing their content to be non-empty. Args: content (str): main message body me (bool): whether to send as an action, where the current account's name prefixes the message rich (bool): whether to send with rich text formatting Returns: .SkypeMsg: copy of the edited message object """ return self.chat.sendMsg(content, me, rich, self.id)
[docs] def delete(self): """ Delete the message and remove it from the conversation. Equivalent to calling :meth:`edit` with an empty ``content`` string. Returns: .SkypeMsg: copy of the deleted message object """ return self.edit("")
[docs]class SkypeTextMsg(SkypeMsg): """ A message containing rich or plain text. Attributes: plain (str): Message content converted to plain text. Hyperlinks are replaced with their target, quotes are converted to the legacy format, and all other HTML tags are stripped from the text. markup (str): Message converted to plain text, retaining formatting markup. Hyperlinks become their target, message edit tags are stripped, and legacy quote text is used. In addition, the following replacements are made: =============================== ========================= Rich text Plain text =============================== ========================= ``<b>bold</b>`` ``*bold*`` ``<i>italic</i>`` ``_italic_`` ``<s>strikethrough</s>`` ``~strikethrough~`` ``<pre>monospace</pre>`` ``{code}monospace{code}`` ``<at id="8:fred.2">Fred</at>`` ``@fred.2`` =============================== ========================= """ @property def plain(self): if self.content is None: return None text = re.sub(r"</?(e|b|i|ss?|pre|quote|legacyquote)\b.*?>", "", self.content) text = re.sub(r"""<a\b.*?href="(.*?)">.*?</a>""", r"\1", text) text = re.sub(r"""<at\b.*?id="8:(.*?)">.*?</at>""", r"@\1", text) text = re.sub(r"""<e_m\b.*?>.*?</e_m>""", r"", text) text = (text.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&") .replace("&quot;", "\"").replace("&apos;", "'")) return text @property def markup(self): if self.content is None: return None text = re.sub(r"</?(e|ss|quote|legacyquote)\b.*?>", "", self.content) text = re.sub(r"</?b\b.*?>", "*", text) text = re.sub(r"</?i\b.*?>", "_", text) text = re.sub(r"</?s\b.*?>", "~", text) text = re.sub(r"</?pre\b.*?>", "{code}", text) text = re.sub(r"""<a\b.*?href="(.*?)">.*?</a>""", r"\1", text) text = re.sub(r"""<at\b.*?id="8:(.*?)">.*?</at>""", r"@\1", text) text = re.sub(r"""<e_m\b.*?>.*?</e_m>""", r"", text) text = (text.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&") .replace("&quot;", "\"").replace("&apos;", "'")) return text
[docs]@SkypeUtils.initAttrs @SkypeUtils.convertIds(users=("contact",)) class SkypeContactMsg(SkypeMsg): """ A message containing one or more shared contacts. Attributes: contacts (:class:`.SkypeUser` list): User objects embedded in the message. contactNames (str list): Names of the users, as seen by the sender of the message. """ attrs = SkypeMsg.attrs + ("contactIds", "contactNames") @classmethod def contentToFields(cls, content): fields = super(SkypeContactMsg, cls).contentToFields(content) fields.update({"contactIds": [], "contactNames": []}) contactTags = content.find_all("c") for tag in contactTags: fields["contactIds"].append(tag.get("s")) fields["contactNames"].append(tag.get("f")) return fields @property def html(self): tag = makeTag("contacts") for con in self.contacts: tag.append(makeTag("c", t="s", s=con.id, f=con.name)) return tag
[docs]@SkypeUtils.initAttrs class SkypeLocationMsg(SkypeMsg): """ A message containing the sender's location. Attributes: latitude (float): North-South coordinate of the user's location. longitude (float): East-West coordinate of the user's location. altitude (int): Vertical position from sea level. speed (int): Current velocity during the reading. course (int): Current direction during the recording. address (str): Geocoded address provided by the sender. mapUrl (str): Link to map displaying the location. """ attrs = SkypeMsg.attrs + ("latitude", "longitude", "altitude", "speed", "course", "address", "mapUrl") @classmethod def contentToFields(cls, content): fields = super(SkypeLocationMsg, cls).contentToFields(content) locTag = content.find("location") for attr in ("latitude", "longitude", "altitude", "speed", "course"): fields[attr] = int(locTag.get(attr)) if locTag.get(attr) else None # Exponent notation produces a float, meaning lat/long will always be floats too. for attr in ("latitude", "longitude"): if fields[attr]: fields[attr] /= 1e6 fields.update({"address": locTag.get("address"), "mapUrl": locTag.find("a").get("href")}) return fields @property def html(self): timestamp = self.time or datetime.now() params = {} for attr in ("latitude", "longitude", "altitude", "speed", "course"): if getattr(self, attr): params[attr] = getattr(self, attr) for attr in ("latitude", "longitude"): if attr in params: params[attr] = int(params[attr] * 1e6) if self.address: params["address"] = self.address params.update({"pointOfInterest": "", "timeStamp": str(int(time.mktime(timestamp.timetuple())))}) tag = makeTag("location", **params) tag.append(makeTag("a", self.address, href=self.mapUrl)) return tag
[docs]@SkypeUtils.initAttrs class SkypeCardMsg(SkypeMsg): """ A message containing an interactive card. Attributes: title (str): Heading text at the top of the card. body (str): Main text content in the card. buttons (:class:`Button` list): Available actions for this card. """
[docs] @SkypeUtils.initAttrs class Button(SkypeObj): """ A clickable button within a card. Attributes: type (str): Type of action to be performed on click. title (str): Text displayed on the button. value (str): Parameter to the action. """ attrs = ("type", "title", "value") @property def data(self): return dict((k, getattr(self, k)) for k in self.attrs)
attrs = SkypeMsg.attrs + ("title", "body", "buttons") @classmethod def contentToFields(cls, content): fields = super(SkypeCardMsg, cls).contentToFields(content) swiftTag = content.find("swift") data = json.loads(base64.b64decode(swiftTag.get("b64"))) card = data.get("attachments", [{}])[0].get("content", {}) fields.update({"title": card.get("title"), "body": card.get("text"), "buttons": [cls.Button(**button) for button in card.get("buttons")]}) return fields @property def html(self): data = {"attachments": [{"content": {"title": self.title, "text": self.body, "buttons": [button.data for button in self.buttons]}, "contentType": "application/vnd.microsoft.card.hero"}], "type": "message/card", "timestamp": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")} if self.chatId: data["recipient"] = {"id": self.chatId} userType, userId = self.chatId.split(":", 1) if userType == "19": # Cards haven't been seen in group conversations yet, so assuming a sensible behaviour. data["recipient"]["name"] = self.chat.topic elif self.skype: data["recipient"]["name"] = str(self.skype.contacts[userId].name) b64 = base64.b64encode(json.dumps(data, separators=(",", ":")).encode("utf-8")).decode("utf-8") tag = makeTag("URIObject", "Card - access it on ", type="SWIFT.1", url_thumbnail="https://urlp.asm.skype.com/v1/url/content?url=https://" "neu1-urlp.secure.skypeassets.com/static/card-128x128.png") tag.append(makeTag("a", "https://go.skype.com/cards.unsupported", href="https://go.skype.com/cards.unsupported")) tag.append(". ") tag.append(makeTag("Swift", b64=b64)) tag.append(makeTag("Description")) return tag
[docs]@SkypeUtils.initAttrs class SkypeFileMsg(SkypeMsg): """ A message containing a file shared in a conversation. Attributes: file (:class:`File`): File object embedded in the message. urlContent (str): URL to retrieve the raw file content. fileContent (bytes): Raw content of the file. """
[docs] @SkypeUtils.initAttrs class File(SkypeObj): """ Details about a file contained within a message. Attributes: name (str): Original filename from the client. size (int): Number of bytes in the file. urlFull (str): URL to retrieve the original file. urlThumb (str): URL to retrieve a thumbnail or display image for the file. urlView (str): URL for the user to access the file outside of the API. """ attrs = ("name", "size", "urlFull", "urlThumb", "urlView") @property def urlAsm(self): # Workaround for Skype providing full URLs with the wrong domain. # api.asm.skype.com/.../0-xxx-yy-zzz... -> xxx-api.asm.skype.com if not self.urlFull: return None match = re.match("{}/0-([a-z0-9]+)-".format(SkypeConnection.API_ASM), self.urlFull) if not match: return self.urlFull prefix = SkypeConnection.API_ASM_LOCAL.format(match.group(1)) return self.urlFull.replace(SkypeConnection.API_ASM, prefix, 1)
attrs = SkypeMsg.attrs + ("file",) contentPath = "original" @classmethod def contentToFields(cls, content): fields = super(SkypeFileMsg, cls).contentToFields(content) # BeautifulSoup converts tag names to lower case, and find() is case-sensitive. file = content.find("uriobject") if file: fileFields = {"name": (file.find("originalname") or {}).get("v"), "size": (file.find("filesize") or {}).get("v"), "urlFull": file.get("uri"), "urlThumb": file.get("url_thumbnail"), "urlView": (file.find("a") or {}).get("href")} fields["file"] = SkypeFileMsg.File(**fileFields) return fields @property def urlContent(self): return "{0}/views/{1}".format(self.file.urlAsm, self.contentPath) if self.file else None @property @SkypeUtils.cacheResult def fileContent(self): if self.file: return self.skype.conn("GET", self.urlContent, auth=SkypeConnection.Auth.Authorize).content else: return None @property def html(self): if not self.file: return "" tag = makeTag("URIObject", type="File.1", uri=self.file.urlFull, url_thumbnail=self.file.urlThumb) tag.append(makeTag("Title", "Title: {0}".format(self.file.name) if self.file.name else "")) tag.append(makeTag("Description", "Description: {0}".format(self.file.name) if self.file.name else "")) tag.append(makeTag("FileSize", v=self.file.size)) tag.append(makeTag("OriginalName", v=self.file.name)) tag.append(makeTag("a", self.file.urlView, href=self.file.urlView)) return tag
[docs]@SkypeUtils.initAttrs class SkypeImageMsg(SkypeFileMsg): """ A message containing a picture shared in a conversation. """ contentPath = "imgpsh_fullsize" @property def html(self): if not self.file: return "" tag = makeTag("URIObject", type="Picture.1", uri=self.file.urlFull, url_thumbnail=self.file.urlThumb) tag.append(makeTag("OriginalName", v=self.file.name)) tag.append(makeTag("a", self.file.urlView, href=self.file.urlView)) tag.append(makeTag("meta", type="photo", originalName=self.file.name)) return tag
[docs]@SkypeUtils.initAttrs class SkypeAudioMsg(SkypeFileMsg): """ A message containing audio shared in a conversation. """ contentPath = "audio" @property def html(self): if not self.file: return "" tag = makeTag("URIObject", type="Audio.1", uri=self.file.urlFull, url_thumbnail=self.file.urlThumb) tag.append(makeTag("OriginalName", v=self.file.name)) tag.append(makeTag("a", self.file.urlView, href=self.file.urlView)) return tag
[docs]@SkypeUtils.initAttrs class SkypeVideoMsg(SkypeFileMsg): """ A message containing a video shared in a conversation. """ contentPath = "video" @property def html(self): if not self.file: return "" tag = makeTag("URIObject", type="Video.1", uri=self.file.urlFull, url_thumbnail=self.file.urlThumb) tag.append(makeTag("OriginalName", v=self.file.name)) tag.append(makeTag("a", self.file.urlView, href=self.file.urlView)) return tag
[docs]@SkypeUtils.initAttrs @SkypeUtils.convertIds("users") class SkypeCallMsg(SkypeMsg): """ A message representing a change in state to a voice or video call inside the conversation. Attributes: state (:class:`.State`): New state of the call. users (:class:`.SkypeUser` list): User objects embedded in the message. userNames (str list): Names of the users, as seen by the initiator of the call. """ State = SkypeEnum("SkypeCallMsg.State", ("Started", "Ended", "Missed")) """ :class:`.SkypeEnum`: Possible call states (either started and incoming, or ended). Attributes: State.Started: New call has just begun. State.Ended: Call failed to connect, or all call participants have hung up. State.Missed: Missed the call as it ended. """ attrs = SkypeMsg.attrs + ("state", "userIds", "userNames") @classmethod def contentToFields(cls, content): fields = super(SkypeCallMsg, cls).contentToFields(content) listTag = content.find("partlist") fields.update({"state": {"started": cls.State.Started, "ended": cls.State.Ended, "missed": cls.State.Missed}.get(listTag.get("type")), "userIds": [], "userNames": []}) for partTag in listTag.find_all("part"): fields["userIds"].append(SkypeUtils.noPrefix(partTag.get("identity"))) fields["userNames"].append(partTag.find("name").text) return fields @property def html(self): partType = {self.State.Started: "started", self.State.Ended: "ended", self.State.Missed: "missed"}[self.state] tag = makeTag("partlist", type=partType, alt="") for user in self.users: conTag = makeTag("part", identity=user.id) conTag.append(makeTag("name", str(user.name))) tag.append(conTag) return tag
[docs]@SkypeUtils.initAttrs class SkypePropertyMsg(SkypeMsg): """ A base message type for changes to conversation properties. """ def tagTemplate(self, root): # Subclasses of this class have the same message structure. timestamp = self.time or datetime.now() tag = makeTag(root) tag.append(makeTag("eventtime", str(int(time.mktime(timestamp.timetuple()))))) tag.append(makeTag("initiator", "8:{0}".format(self.userId) if self.userId else "")) return tag
[docs]@SkypeUtils.initAttrs class SkypeTopicPropertyMsg(SkypePropertyMsg): """ A message representing a change in a group conversation's topic. Attributes: topic (str): Description of the conversation, shown to all participants. """ attrs = SkypeMsg.attrs + ("topic",) @classmethod def contentToFields(cls, content): fields = super(SkypeTopicPropertyMsg, cls).contentToFields(content) propInfo = content.find("topicupdate") if propInfo: fields.update({"userId": SkypeUtils.noPrefix(propInfo.find("initiator").text), "topic": propInfo.find("value").text}) return fields @property def html(self): tag = self.tagTemplate("topicupdate") tag.append(makeTag("value", self.topic)) return tag
[docs]@SkypeUtils.initAttrs class SkypeOpenPropertyMsg(SkypePropertyMsg): """ A message representing a change to joining the conversation by link. Attributes: open (bool): Whether new participants can join via a public join link. """ attrs = SkypeMsg.attrs + ("open",) @classmethod def contentToFields(cls, content): fields = super(SkypeOpenPropertyMsg, cls).contentToFields(content) propInfo = content.find("joiningenabledupdate") if propInfo: fields.update({"userId": SkypeUtils.noPrefix(propInfo.find("initiator").text), "open": propInfo.find("value").text == "true"}) return fields @property def html(self): tag = self.tagTemplate("joiningenabledupdate") tag.append(makeTag("value", "true" if self.open else "false")) return tag
[docs]@SkypeUtils.initAttrs class SkypeHistoryPropertyMsg(SkypePropertyMsg): """ A message representing a change to history disclosure. Attributes: history (bool): Whether message history is provided to new participants. """ attrs = SkypeMsg.attrs + ("history",) @classmethod def contentToFields(cls, content): fields = super(SkypeHistoryPropertyMsg, cls).contentToFields(content) propInfo = content.find("historydisclosedupdate") if propInfo: fields.update({"userId": SkypeUtils.noPrefix(propInfo.find("initiator").text), "history": propInfo.find("value").text == "true"}) return fields @property def html(self): tag = self.tagTemplate("historydisclosedupdate") tag.append(makeTag("value", "true" if self.history else "false")) return tag
[docs]@SkypeUtils.initAttrs @SkypeUtils.convertIds(user=("member",)) class SkypeMemberMsg(SkypePropertyMsg): """ A message representing a change in a group conversation's participants. Note that Skype represents these messages as being sent *by the conversation*, rather than the initiator. Instead, :attr:`user <SkypeMsg.user>` is set to the initiator, and :attr:`member` to the target. Attributes: member (:class:`.SkypeUser`): User being added to or removed from the conversation. """ attrs = SkypeMsg.attrs + ("memberId",)
[docs]@SkypeUtils.initAttrs class SkypeAddMemberMsg(SkypeMemberMsg): """ A message representing a user added to a group conversation. """ @classmethod def contentToFields(cls, content): fields = super(SkypeAddMemberMsg, cls).contentToFields(content) memInfo = content.find("addmember") if memInfo: fields.update({"userId": SkypeUtils.noPrefix(memInfo.find("initiator").text), "memberId": SkypeUtils.noPrefix(memInfo.find("target").text)}) return fields @property def html(self): tag = self.tagTemplate("addmember") tag.append(makeTag("target", "8:{0}".format(self.memberId) if self.memberId else "")) return tag
[docs]@SkypeUtils.initAttrs class SkypeChangeMemberMsg(SkypeMemberMsg): """ A message representing a user's role being changed within a group conversation. Attributes: admin (bool): Whether the change now makes the user an admin. """ attrs = SkypeMemberMsg.attrs + ("admin",) @classmethod def contentToFields(cls, content): fields = super(SkypeChangeMemberMsg, cls).contentToFields(content) memInfo = content.find("roleupdate") if memInfo: fields.update({"userId": SkypeUtils.noPrefix(memInfo.find("initiator").text), "memberId": SkypeUtils.noPrefix(memInfo.find("target").find("id").text), "admin": memInfo.find("target").find("role").text == "admin"}) return fields @property def html(self): tag = self.tagTemplate("addmember") valTag = makeTag("target") valTag.append(makeTag("id", "8:{0}".format(self.memberId) if self.memberId else "")) valTag.append(makeTag("role", "admin" if self.admin else "user")) tag.append(valTag) return tag
[docs]@SkypeUtils.initAttrs class SkypeRemoveMemberMsg(SkypeMemberMsg): """ A message representing a user removed from a group conversation. """ @classmethod def contentToFields(cls, content): fields = super(SkypeRemoveMemberMsg, cls).contentToFields(content) memInfo = content.find("deletemember") if memInfo: fields.update({"userId": SkypeUtils.noPrefix(memInfo.find("initiator").text), "memberId": SkypeUtils.noPrefix(memInfo.find("target").text)}) return fields @property def html(self): tag = self.tagTemplate("addmember") tag.append(makeTag("target", "8:{0}".format(self.memberId) if self.memberId else "")) return tag