# -*- coding: utf-8 -*-
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
from six import string_types
[docs]class Channel(object):
"""
Represents a chat channel managed by a space
A channel is a group of interactions within a chat space. It features
a unique identifier, and a title. It also have participants and content.
This class is a general abstraction of a communication channel, that can
easily been adapted to various chat systems. It has been designed as a
dictionary wrapper, with minimum exposure to shellbot, while
enabling the transmission of rich information through serialization.
Instances of Channel are created within a Space object, and consumed by it
as well.
For example, to create a channel with a given title, you could write::
channel = space.create(title='A new channel')
bot.say(u"I am happy to join {}".format(channel.title))
And to change the title of the channel::
channel.title = 'An interesting place'
space.update(channel)
Direct channels support one-to-one interactions between the bot and one
person. The creation of a direct channel can only be indirect, by sending
an invitation to the target person. For example::
bot.say(person='foo.bar@acme.com',
text='Do you want to deal with me?')
If the person receives and accepts the invitation, the engine will receive
a ``join`` event and load a new bot devoted to the direct channel. So, at
the end of the day, when multiple persons interact with a shellbot, this
involve both group and direct channels.
For example, if you create a shellbot named shelly, that interacts with
Alice and with Bob, then shelly will overlook multiple bots and channels:
* shelly main channel (bot + channel + store + state machine)
* direct channel with Alice (bot + channel + store + state machine)
* direct channel with Bob (bot + channel + store + state machine)
"""
def __init__(self,
attributes=None):
"""
Represents a channel generated by a chat space
:param attributes: the set of atributes of this event
:type attributes: dict or json-encoded string
This function may raise AttributeError if some mandatory
attribute is missing.
"""
if not attributes:
self.__dict__['attributes'] = {}
elif isinstance(attributes, string_types):
self.__dict__['attributes'] = json.loads(attributes)
else:
self.__dict__['attributes'] = attributes
def __getattr__(self, key):
"""
Provides access to any native attribute
:param key: name of the attribute
:type key: str
:return: the value of the attribute
This method is called when attempting to access a object attribute that
hasn't been defined for the object. For example trying to access
``object.attribute1`` when ``attribute1`` hasn't been defined.
``Channel.__getattr__()`` checks original attributes to see if the
attribute exists, and returns its value. Else an
AttributeError is raised.
"""
try:
return self.attributes[key]
except KeyError:
raise AttributeError(u"'{}' has no attribute '{}'".format(
self.__class__.__name__, key))
def __setattr__(self, key, value):
"""
Changes an attribute
:param key: name of the attribute
:type key: str
:param value: new value of the attribute
:type value: str or other serializable object
The use case for this function is when you adapt a channel that does
not feature an attribute that is expected by shellbot.
"""
self.attributes[key] = value
[docs] def get(self, key, default=None):
"""
Returns the value of one attribute
:param key: name of the attribute
:type key: str
:param default: default value of the attribute
:type default: str or other serializable object
:return: value of the attribute
:rtype: str or other serializable object or None
The use case for this function is when you adapt a channel that does
not feature an attribute that is expected by shellbot.
More specifically, call this function on optional attributes so as
to avoid AttributeError
"""
value = self.attributes.get(key) # do not use default here!
if value is None:
value = default
return value
def __repr__(self):
"""
Returns a string representing this object as valid Python expression.
"""
return u"{}({})".format(
self.__class__.__name__,
json.dumps(self.attributes, sort_keys=True))
def __str__(self):
"""
Returns a human-readable string representation of this object.
"""
return json.dumps(self.attributes, sort_keys=True)
def __eq__(self, other):
"""
Compares with another object
"""
try:
if self.attributes != other.attributes:
return False
return True
except:
return False # not same duck types
@property
def id(self):
"""
Returns channel unique id
:rtype: str
"""
return self.__getattr__('id')
@property
def title(self):
"""
Returns channel title
:rtype: str
"""
return self.__getattr__('title')
@property
def is_moderated(self):
"""
Indicates if this channel is moderated
:rtype: str
A channel is moderated when some participants have specific powers
that others do not have. Else all participants are condidered the
same and peer with each others.
"""
return self.attributes.get('is_moderated', False)
@property
def is_direct(self):
"""
Indicates if this channel is only for one person and the bot
:rtype: str
A channel is deemed direct when it is reserved to one-to-one
interactions. Else it is considered a group channel, with potentially
many participants.
"""
return self.attributes.get('is_direct', False)