예제 #1
0
class BotWrapper:
    def __init__(self,
                 url,
                 magic_phrase,
                 max_turns=10,
                 callback=None,
                 callback_params=1,
                 msg_q=False):
        print('starting service')
        self.start_proba = 1.0
        self.magic_phrase = magic_phrase
        self.url = replace_localhost(url)
        self.bot = Alice()
        self.max_turns = max_turns
        self.sending_message = False
        self._id = None
        self.use_msg_q = msg_q  # msg_q sets whether or not we are queueing messages
        self.websocket = 'ws://%s/websocket' % self.url
        self.client = MeteorClient(self.websocket)
        self.client.ddp_client.ddpsocket.extra_headers = [('Bot', 'true')]
        print(self.client.ddp_client.ddpsocket.handshake_headers)
        self.client.connect()

        self.idle_time = 3 * 60
        self.thread_time = 2
        self.max_retry = 3

    def restart_idler(self):
        ''' Restarts the idle watcher '''
        print('restarting idler')
        if hasattr(self, 'idler_thread') and self.idler_thread:
            self.idler_thread.cancel()
        self.idler_thread = threading.Timer(self.idle_time,
                                            self.idle_user_handler)
        self.idler_thread.start()

    def idle_user_handler(self):
        """ Handler that disconnects conversation in the event that a user leaves """

        print('user is idle disconnect')
        self.idler_thread = None
        self.end_convo()

    def login(self,
              user='******',
              pwd='botbot',
              callback=None,
              callback_params=0):
        print('logging in')

        def set_user(data):
            self.set_user_id(data['id'])
            print('user id set to', self._id)
            if callback and callback_params == 1:
                print('running callback with 1 parameter')
                callback(self)
            elif callback and callback_params == 0:
                callback()

        # TODO make this into threading timers.
        while not self._id:
            self.client.login(user,
                              pwd,
                              callback=func_wrap(set_user, params=1))
            time.sleep(0.5)

    def logout(self):
        self.client.logout()


#    def find_and_join_room(self):
#        """ Finds a room and joins it """
#        self.find_room(callback=(lambda roomId : self.join_room(roomId)))
#
#    def find_room(self, callback=None):
#        print('looking for an open room')
#        def room_callback():
#            print('looking for a room')
#            user = self.client.find_one('users')
#            print('user dict',user.items())
#            if user["in_convo"]:
#                roomObj = user["curConvo"]
#                print('roomid: ', roomObj)
#            else:
#                openrooms = self.client.find('convos') # {curSessions : {$lt  :2}}
#                roomObj = openrooms[0] if openrooms and len(openrooms) > 0 else -1
#
#            # TODO may have issues with room id when user is in convo
#            if roomObj != -1:
#                if type(roomObj) == str:
#                    print(roomObj, 'room')
#                    print('openrooms', openrooms)
#                callback(roomObj['_id'])
#                # Add user to room
#
#            else:
#                print('No rooms found. Back to the bat cave')
#        self.subscribe('currentUser',params=[], callback=func_wrap(
#            lambda : room_callback()
#            )
#        )

    def subscribe(self, collection, params=[], callback=None):
        """ Wrapper for subscribe to avoid issues with already subscribed rooms """
        try:
            print("subscribing to {}".format(collection))
            self.client.subscribe(collection, params, callback)
        except MeteorClientException:
            print(
                'Already subscribed to {}. Running callback with None'.format(
                    collection))
            if callback:
                callback(None)

    def join_room(self, roomId, otherUserId, callback=None):
        """ Join a room based on roomId """
        print('join room with id', roomId)
        self.roomId = roomId
        self.msg_queue = []
        self.available = False
        self.client.call(
            'convos.addUserToRoom',
            params=[roomId, self.magic_phrase],
            callback=func_wrap(lambda: self.subscribe(
                'chat', [roomId],
                func_wrap(lambda: self.subscribe(
                    'msgs', [roomId],
                    func_wrap(lambda: self.subscribe(
                        'currentUsers', [roomId],
                        func_wrap(lambda: self.watch_room(
                            roomId,
                            func_wrap(lambda: self.send_ready(
                                roomId, otherUserId, callback)))))))))))

    def send_ready(self, roomId, otherUserId, callback=None):
        self.client.call('convos.botReady',
                         params=[roomId, otherUserId, self.magic_phrase],
                         callback=callback)

    def unsubscribe(self, collection):
        """ Unsubscribe from the collection """
        try:
            self.client.unsubscribe(collection)
        except MeteorClientException:
            print('\t"{}" not subscribed to.'.format(collection))

    def end_convo(self):
        """ End the conversation """
        print('end conversation and unsubscribe from it all')
        self.client.remove_all_listeners('added')
        self.client.remove_all_listeners('changed')

        self.unsubscribe('chat')
        self.unsubscribe('msgs')
        self.unsubscribe('currentUsers')

        self.client.call('users.exitConvo', [])
        self.client.call('convos.updateRatings', [self.roomId, 'not'])
        self.available = True
        if hasattr(self, 'idler_thread') and self.idler_thread:
            self.idler_thread.cancel()

    def set_wpm(self):
        """ Set the words per minute of the bot """
        wpm = random.randint(150, 200)
        self.cps = 60 / (wpm * 5)
        print('Setting wpm : {} '.format(wpm))

    def prime_bot(self, convo_obj):
        """  the conversational bot """
        print('convo_obj', convo_obj)
        input_msg = 'hi'
        if 'msgs' in convo_obj and convo_obj['msgs']:
            topic_msg_id = convo_obj['msgs'][0]
            msg_obj = self.client.find_one('messages',
                                           selector={'_id': topic_msg_id})
            if msg_obj:
                input_msg = msg_obj['message']

        msg = self.bot.message(input_msg, self.roomId)
        if random.random() > self.start_proba:
            self.send_message(msg)

    def watch_room(self, roomId, callback=None):
        """
        Setup Event Listeneres for a room and checks to make sure that the room is updating
        """
        self.turns = 0
        convo_obj = self.client.find_one('convos', selector={'_id': roomId})
        self.room_closed = convo_obj['closed']
        self.set_wpm()

        self.last_message = ""
        self.confirmed_messages = [
        ]  # all messages sent by the user that have been confirmed
        self.thread = MessageHandlerThread(self)

        def message_added(collection, id, fields):
            """ callback for when a message is added """
            if (collection == 'messages' and 'message' in fields
                    and 'user' in fields):
                print(type(self._id), type(fields['user']), self._id,
                      fields['user'])
                if fields['user'] != self._id and self.last_message != fields[
                        'message']:
                    self.restart_idler()
                    self.receive_message(fields['message'])
                    self.last_message = fields['message']
                    self.thread.message_received = True
                elif fields['user'] == self._id:
                    print('\t messages from self detected')
                    self.confirmed_messages.append(fields['message'])

        self.client.on('added', message_added)

        def watch_convo(collection, id, fields, cleared):
            """ callback for when any part of the conversation is updated """
            if self.roomId and collection == "convos" and id == self.roomId:
                # print('\t',fields)
                if 'closed' in fields:
                    print('\tRoom is closed: ', fields['closed'])
                    self.room_closed = fields['closed']
                    self.end_convo()
                if 'msgs' in fields:
                    print('\tMessages updated in convo "{}"'.format(id))
                    # TODO this is bugggy
                    self.thread.convo_updated = True
                if 'turns' in fields:
                    print('\tTurns updated to "{}"'.format(fields['turns']))
                    self.turns = fields['turns']
            elif self.roomId == id:
                print(collection, id, fields)

        self.client.on('changed', watch_convo)
        # mark the bot as ready to talk
        self.restart_idler()
        self.prime_bot(convo_obj)
        print("before thread")
        self.thread.start()
        print("after thread")

        if callback:
            callback(None)

    def respond(self):
        """ Kind of a hacky way to respond to the conversation """
        print("responding")
        if self.msg_queue and self.use_msg_q:
            partner_msg = self.msg_queue[0]
            self.msg_queue = self.msg_queue[1:]
            msg = self.bot.message(partner_msg, self.roomId)
            print(msg)
            self.send_message(msg)

        if self.msg_queue and not self.sending_message:
            partner_msg = self.msg_queue[-1]
            self.msg_queue = self.msg_queue[:-1]
            msg = self.bot.message(partner_msg, self.roomId)
            print(msg)
            self.send_message(msg)

    def still_in_conv(self):
        """ Returns whether the conversation is still moving """
        in_conv = self.roomId != None and not self.client.find_one(
            'convos', selector={'_id': self.roomId})['closed']
        print('\tstill in conv', in_conv)
        if not in_conv:
            self.end_convo()
        print(
            '\tclosed: ',
            self.client.find_one('convos', selector={'_id':
                                                     self.roomId})['closed'])
        return in_conv

    def get_convo_dict(self):
        if self.roomId:
            return self.client.find_one('convos',
                                        selector={'_id': self.roomId})
        else:
            return {}

    def get_message(self, idx):
        ''' Returns the message at idx'''
        convo_dict = self.get_convo_dict()
        if convo_dict:
            topic_msg_id = convo_dict['msgs'][idx]
            msg_dict = self.client.find_one('messages',
                                            selector={'_id': topic_msg_id})
            # print(msg_dict)
            if msg_dict:
                return msg_dict['message']
        return ''

    def received_message(self, message):
        """ Checks whether the bot actually sent the message """
        # TODO add handler that removes a confirmed message to save memory
        return message in self.confirmed_messages

    def retry_message(self, message, retry=0, callback=None):
        """ Handler that makes attempts to connect a user back into a conversation """
        # TODO set as properties
        if retry == 0 or not self.received_message(
                message) and retry < self.max_retry:
            self.update_conversation(message, callback)

            if retry != 0:
                print('\t\tRetry {} of sending "{}"'.format(retry, message))

            t = threading.Timer(self.thread_time,
                                lambda: self.retry_message(message, retry + 1))
            t.start()
        elif retry >= self.max_retry:
            print(
                '\tMax retries reached - couldn\'t verify whether {} was received'
                .format(message))
        else:
            print('\t"{}" successfully received'.format(message))

    def update_conversation(self, message, callback=None):
        self.client.call('convos.updateChat', [message, self.roomId], callback)

    def _send_message(self, message, callback=None):
        self.last_message_sent = message
        if self.still_in_conv():
            self.retry_message(message, callback=callback)
        else:
            print('Not responding - conversation is OVER')
        self.sending_message = False

    def send_message(self, message, callback=None):
        # calculates typing speed based on rough cps for user
        sleep_time = self.cps * len(message)
        print("Preparing to send '{}' Waiting '{}' seconds.".format(
            message, sleep_time))
        t = threading.Timer(sleep_time,
                            lambda: self._send_message(message, callback))
        t.start()

    def receive_message(self, message):
        """ Called whenever the bot receives a message """
        print('Received "{}"'.format(message))
        self.msg_queue.append(message)
        # message = 'sup then' # self.bot.message(message)

        # self.send_message(message)

    def set_user_id(self, id):
        self.available = True
        print('set user id to ', id)
        self._id = id
예제 #2
0
class DicecloudClient:
    instance = None
    user_id = None

    def __init__(self, debug=False):
        self.meteor_client = MeteorClient(SOCKET_BASE, debug=debug)
        self.http = DicecloudHTTP(API_BASE, API_KEY, debug=debug)
        self.logged_in = False

    @classmethod
    def getInstance(cls):
        if cls.instance is None:
            try:
                cls.instance = cls(debug=TESTING)
                cls.instance.initialize()
            except:
                return None
        return cls.instance

    def initialize(self):
        log.info(f"Initializing Dicecloud Meteor client (debug={TESTING})")
        self.meteor_client.connect()
        loops = 0
        while (not self.meteor_client.connected) and loops < 100:
            time.sleep(0.1)
            loops += 1
        log.info(f"Connected to Dicecloud in {loops/10} seconds")

        def on_login(error, data):
            if data:
                type(self).user_id = data.get('id')
                self.logged_in = True
            else:
                log.warning(error)
                raise LoginFailure()

        self.meteor_client.login(UNAME, PWD, callback=on_login)
        loops = 0
        while not self.logged_in and loops < 100:
            time.sleep(0.1)
            loops += 1
        log.info(f"Logged in as {self.user_id}")

    async def ensure_connected(self):
        if self.logged_in:  # everything is fine:tm:
            return
        asyncio.get_event_loop().run_in_executor(None, self.initialize)

    async def _get_list_id(self, character, list_name=None):
        """
        :param character: (Character) the character to get the spell list ID of.
        :param list_name: (str) The name of the spell list to look for. Returns default if not passed.
        :return: (str) The default list id.
        """
        char_id = character.upstream[10:]

        char = await self.get_character(char_id)
        if list_name:
            list_id = next((l for l in char.get('spellLists', [])
                            if l['name'].lower() == list_name.lower()), None)
        else:
            list_id = next((l for l in char.get('spellLists', [])), None)
        return list_id

    async def get_character(self, charId):
        return await self.http.get(f'/character/{charId}/json')

    async def add_spell(self, character, spell):
        """Adds a spell to the dicecloud list."""
        return await self.add_spells(character, [spell])

    async def add_spells(self, character, spells, spell_list=None):
        """
        :param character: (Character) The character to add spells for.
        :param spells: (list) The list of spells to add
        :param spell_list: (str) The spell list name to search for in Dicecloud.
        """
        list_id = await self._get_list_id(character, spell_list)
        if not list_id:  # still
            raise InsertFailure(
                "No matching spell lists on origin sheet. Run `!update` if this seems incorrect."
            )
        return await self.http.post(
            f'/api/character/{character.upstream[10:]}/spellList/{list_id}',
            [s.to_dicecloud() for s in spells])

    async def create_character(self,
                               name: str = "New Character",
                               gender: str = None,
                               race: str = None,
                               backstory: str = None):
        data = {'name': name, 'writers': [self.user_id]}
        if gender is not None:
            data['gender'] = gender
        if race is not None:
            data['race'] = race
        if backstory is not None:
            data['backstory'] = backstory

        data['settings'] = {'viewPermission': 'public'}  # sharing is caring!
        response = await self.http.post('/api/character', data)
        return response['id']

    async def delete_character(self, charId: str):
        await self.http.delete(f'/api/character/{charId}')

    async def get_user_id(self, username: str):
        username = urllib.parse.quote_plus(username)
        userId = await self.http.get(f'/api/user?username={username}')
        return userId['id']

    async def transfer_ownership(self, charId: str, userId: str):
        await self.http.put(f'/api/character/{charId}/owner', {'id': userId})

    async def insert_feature(self, charId, feature):
        return (await self.insert_features(charId, [feature]))[0]

    async def insert_features(self, charId: str, features: list):
        response = await self.http.post(f'/api/character/{charId}/feature',
                                        [f.to_dict() for f in features])
        return response

    async def insert_proficiency(self, charId, prof):
        return (await self.insert_proficiencies(charId, [prof]))[0]

    async def insert_proficiencies(self, charId: str, profs: list):
        response = await self.http.post(f'/api/character/{charId}/prof',
                                        [p.to_dict() for p in profs])
        return response

    async def insert_effect(self, charId, effect):
        return (await self.insert_effects(charId, [effect]))[0]

    async def insert_effects(self, charId: str, effects: list):
        response = await self.http.post(f'/api/character/{charId}/effect',
                                        [e.to_dict() for e in effects])
        return response

    async def insert_class(self, charId, klass):
        return (await self.insert_classes(charId, [klass]))[0]

    async def insert_classes(self, charId: str, classes: list):
        response = await self.http.post(f'/api/character/{charId}/class',
                                        [c.to_dict() for c in classes])
        return response
예제 #3
0
class DicecloudClient:
    instance = None
    user_id = None

    def __init__(self,
                 username,
                 password,
                 api_key,
                 debug=False,
                 no_meteor=False):
        self.username = username
        self.encoded_password = password.encode() if password else None
        if not no_meteor:
            self.meteor_client = MeteorClient(SOCKET_BASE, debug=debug)
        else:
            self.meteor_client = None
        self.http = DicecloudHTTP(API_BASE, api_key, debug=debug)
        self.logged_in = False
        self.debug = debug

    def initialize(self):
        if self.meteor_client is None:
            log.info(f"Initialized without Meteor.")
            return
        log.info(f"Initializing Dicecloud Meteor client (debug={TESTING})")
        self.meteor_client.connect()
        loops = 0
        while (not self.meteor_client.connected) and loops < 100:
            time.sleep(0.1)
            loops += 1
        log.info(f"Connected to Dicecloud in {loops/10} seconds")

        def on_login(error, data):
            if data:
                type(self).user_id = data.get('id')
                self.logged_in = True
            else:
                log.warning(error)
                raise LoginFailure()

        self.meteor_client.login(self.username,
                                 self.encoded_password,
                                 callback=on_login)
        loops = 0
        while not self.logged_in and loops < 100:
            time.sleep(0.1)
            loops += 1
        log.info(f"Logged in as {self.user_id}")

    def ensure_connected(self):
        if self.logged_in:  # everything is fine:tm:
            return
        self.initialize()

    def _get_list_id(self, character_id, list_name=None):
        """
        :param character: (str) the character to get the spell list ID of.
        :param list_name: (str) The name of the spell list to look for. Returns default if not passed.
        :return: (str) The default list id.
        """
        char = self.get_character(character_id)
        if list_name:
            spell_list = next((l for l in char.get('spellLists', [])
                               if l['name'].lower() == list_name.lower()),
                              None)
        else:
            spell_list = next((l for l in char.get('spellLists', [])), None)
        if not spell_list:
            raise InsertFailure("No spell list found on sheet.")
        return spell_list['_id']

    def get_character(self, char_id):
        return self.http.get(f'/character/{char_id}/json')

    def add_spell(self, character, spell):
        """Adds a spell to the dicecloud list."""
        return self.add_spells(character, [spell])

    def add_spells(self, character_id, spells, spell_list=None):
        """
        :param character_id: (str) The character to add spells for.
        :param spells: (list) The list of spells to add
        :param spell_list: (str) The spell list name to search for in Dicecloud.
        """
        list_id = self._get_list_id(character_id, spell_list)
        if not list_id:  # still
            raise InsertFailure("No matching spell lists on origin sheet.")
        return self.http.post(
            f'/api/character/{character_id}/spellList/{list_id}',
            [s.to_dict() for s in spells])

    def create_character(self,
                         name: str = "New Character",
                         gender: str = None,
                         race: str = None,
                         backstory: str = None):
        data = {'name': name}
        if gender is not None:
            data['gender'] = gender
        if race is not None:
            data['race'] = race
        if backstory is not None:
            data['backstory'] = backstory

        data['settings'] = {'viewPermission': 'public'}  # sharing is caring!
        response = self.http.post('/api/character', data)
        return response['id']

    def delete_character(self, char_id: str):
        self.http.delete(f'/api/character/{char_id}')

    def get_user_id(self, username: str):
        username = urllib.parse.quote_plus(username)
        user_id = self.http.get(f'/api/user?username={username}')
        return user_id['id']

    def transfer_ownership(self, char_id: str, user_id: str):
        self.http.put(f'/api/character/{char_id}/owner', {'id': user_id})

    def insert_feature(self, char_id, feature):
        return (self.insert_features(char_id, [feature]))[0]

    def insert_features(self, char_id: str, features: list):
        response = self.http.post(f'/api/character/{char_id}/feature',
                                  [f.to_dict() for f in features])
        return response

    def insert_proficiency(self, char_id, prof):
        return (self.insert_proficiencies(char_id, [prof]))[0]

    def insert_proficiencies(self, char_id: str, profs: list):
        response = self.http.post(f'/api/character/{char_id}/prof',
                                  [p.to_dict() for p in profs])
        return response

    def insert_effect(self, char_id, effect):
        return (self.insert_effects(char_id, [effect]))[0]

    def insert_effects(self, char_id: str, effects: list):
        response = self.http.post(f'/api/character/{char_id}/effect',
                                  [e.to_dict() for e in effects])
        return response

    def insert_class(self, char_id, klass):
        return (self.insert_classes(char_id, [klass]))[0]

    def insert_classes(self, char_id: str, classes: list):
        response = self.http.post(f'/api/character/{char_id}/class',
                                  [c.to_dict() for c in classes])
        return response
예제 #4
0
import os

import time
from MeteorClient import MeteorClient

UNAME = os.environ.get('DICECLOUD_USER', '')
PWD = os.environ.get('DICECLOUD_PWD', '').encode()

client = MeteorClient('ws://dicecloud.com/websocket', debug=True)
client.connect()
print("Connected")
while not client.connected:
    time.sleep(0.1)
client.login(UNAME, PWD)
print("Logged in")

time.sleep(1)  # wait until users collection has updated

USER_ID = client.find_one('users', selector={'username': UNAME}).get('_id')
print("User ID: " + USER_ID)

char_id = 'Mtx98jb3c3wWcrWPj'


def main():
    # check_char()
    test_id()


def test_id():
    client.insert('characters', {
예제 #5
0

if __name__ == '__main__':
    meteor_websocket = "ws://staging.spur.site/websocket"
    mc = MeteorClient(meteor_websocket)
    mc.on('connected', mcConnected)
    mc.on('logging_in', mcLoggingIn)
    mc.on('logged_in', mcLoggedIn)
    mc.on('logged_out', mcLoggedOut)
    mc.on('failed', mcFailed)
    mc.on('closed', mcClosed)
    mc.on('subscribed', mcSubscribed)
    mc.on('added', mcAdded)
    mc.on('changed', mcChanged)
    mc.on('removed', mcRemoved)
    print("{}: Connecting".format(nicetime(time.time())))
    event = threading.Event()
    mc.connect()
    subscribe()
    event.wait()
    print("{}: Returned from subscribe".format(nicetime(time.time())))
    event.clear()
    event = threading.Event()
    mc.login("*****@*****.**",
             "Mucht00f@r",
             callback=mcLoginCheck)
    event.wait()
    print("{}: Returned from login".format(nicetime(time.time())))
    event.clear()
    sys.exit()
class AoikRocketChatErrbot(ErrBot):
    """
    Errbot backend for Rocket.Chat.

    The backend logs in as a Rocket.Chat user, receiving and sending messages.
    """
    def __init__(self, config):
        """
        Constructor.

        :param config: Errbot's config module.

        :return: None.
        """
        # Call super method
        super(AoikRocketChatErrbot, self).__init__(config)

        # Get the backend's config object
        self._config_obj = getattr(self.bot_config, _CONFIG_OBJ_KEY, None)

        # Get logging level from env variable or config object
        log_level = orig_log_level = self._get_config(
            CONFIG_KEYS.BOT_LOG_LEVEL, None)

        # If not specified
        if log_level is None:
            # Get logging level from config module
            log_level = orig_log_level = getattr(self.bot_config,
                                                 CONFIG_KEYS.BOT_LOG_LEVEL,
                                                 None)

            # If not specified
            if log_level is None:
                # Use default
                log_level = logging.DEBUG

        # If the logging level is string, e.g. 'DEBUG'.
        # This means it may be an attribute name of the `logging` module.
        if isinstance(log_level, str):
            # Get attribute value from the `logging` module
            log_level = getattr(logging, log_level, None)

        # Error message
        error_msg = None

        # If the logging level is not int
        if not isinstance(log_level, int):
            # Get message
            error_msg = 'Config `BOT_LOG_LEVEL` value is invalid: {}'.format(
                repr(orig_log_level))

            # Log message
            self._log_error(error_msg)

            # Raise error
            raise ValueError(error_msg)

        # Get logger
        self._logger = logging.getLogger('aoikrocketchaterrbot')

        # Set logging level
        self._logger.setLevel(log_level)

        # Get message
        msg = '# ----- Created logger -----\nBOT_LOG_LEVEL: {}'.format(
            log_level)

        # Log message
        self._logger.debug(msg)

        # Get rocket chat server URI
        self._server_uri = self._get_config(CONFIG_KEYS.SERVER_URI)

        # If server URI is not specified
        if self._server_uri is None:
            # Get message
            error_msg = 'Missing config `SERVER_URI`.'

            # Log message
            self._log_error(error_msg)

            # Raise error
            raise ValueError(error_msg)

        # Get login username
        self._login_username = self._get_config(CONFIG_KEYS.LOGIN_USERNAME)

        # If login username is not specified
        if self._login_username is None:
            # Get message
            error_msg = 'Missing config `LOGIN_USERNAME`.'

            # Log message
            self._log_error(error_msg)

            # Raise error
            raise ValueError(error_msg)

        # Get login password
        self._login_password = self._get_config(CONFIG_KEYS.LOGIN_PASSWORD)

        # If login password is not specified
        if self._login_password is None:
            # Get message
            error_msg = 'Missing config `LOGIN_PASSWORD`.'

            # Log message
            self._log_error(error_msg)

            # Raise error
            raise ValueError(error_msg)

        # If login password is not bytes
        if not isinstance(self._login_password, bytes):
            # Convert login password to bytes
            self._login_password = bytes(self._login_password, 'utf-8')

        # Create login user's identifier object.
        #
        # This attribute is required by superclass.
        #
        self.bot_identifier = self.build_identifier(self._login_username)

        # Event set when the the meteor client has done topic subscribing.
        #
        # When the event is set, the meteor client is in one of the two states:
        # - The topic subscribing has succeeded and the meteor client has
        #   started handling messages.
        # - The topic subscribing has failed and the meteor client has been
        #   closed.
        #
        # The rationale is that the loop at 65ZNO uses the meteor client's
        # `connected` attribute to decide whether continue, and the attribute
        # value is ready for use only after this event is set.
        self._subscribing_done_event = Event()

        # Event set when the meteor client calls the `closed` callback at
        # 3DMYH.
        #
        # The rationale is that the main thread code at 5W6XQ has to wait until
        # the meteor client is closed and the `closed` callback hooked at 7MOJX
        # is called. This ensures the cleanup is fully done.
        #
        self._meteor_closed_event = Event()

    @property
    def mode(self):
        """
        Get mode name.

        :return: Mode name.
        """
        # Return mode name
        return 'aoikrocketchaterrbot'

    def _log_debug(self, msg):
        """
        Log debug message.

        :param msg: Message to log.

        :return: None.
        """
        # Log the message
        self._logger.debug(msg)

    def __hash__(self):
        """Bots are now stored as a key in the bot so they need to be hashable."""
        return id(self)

    def _log_error(self, msg):
        """
        Log error message.

        :param msg: Message to log.

        :return: None.
        """
        # Log the message
        self._logger.error(msg)

    def _get_config(self, key, default=None):
        """
        Get config value from env variable or config object.

        Env variable takes precedence.

        :param key: Config key.

        :param default: Default value.

        :return: Config value.
        """
        # Get env variable name
        env_var_name = _ENV_VAR_NAME_PREFIX + key

        # Get config value from env variable
        config_value = os.environ.get(env_var_name, None)

        # If not specified
        if config_value is None:
            # If not have config object
            if self._config_obj is None:
                # Use default
                config_value = default

            # If have config object
            else:
                # Get config value from config object
                config_value = getattr(self._config_obj, key, default)

        # Return config value
        return config_value

    def _get_bool_config(self, key, default=None):
        """
        Get boolean config value from env variable or config object.

        Env variable takes precedence.

        :param key: Config key.

        :param default: Default value.

        :return: Config value.
        """
        # Get config value
        config_value = self._get_config(key=key, default=default)

        # If config value is false.
        # This aims to handle False, 0, and None.
        if not config_value:
            # Return False
            return False

        # If config value is not false
        else:
            # Get config value's string in lower case
            config_value_str_lower = str(config_value).lower()

            # Consider '0', case-insensitive 'false' and 'no' as false,
            # otherwise as true.
            return config_value_str_lower not in ['0', 'false', 'no']

    def _patch_meteor_client(self):
        """
        Patch meteor client to fix an existing bug.

        :return: None.
        """
        # Get whether need patch meteor client. Default is True.
        need_patch = self._get_bool_config(CONFIG_KEYS.PATCH_METEOR_CLIENT,
                                           True)

        # If need patch meteor client
        if need_patch:
            # Create `change_data` function
            def change_data(self, collection, id, fields, cleared):
                """
                Callback called when data change occurred.

                :param self: CollectionData object.

                :param collection: Data collection key.

                :param id: Data item key.

                :param fields: Data fields changed.

                :param cleared: Data fields to be cleared.

                :return None.
                """
                # If the data collection key not exists
                #
                # The original `change_data` function assumes it is existing,
                # but it is not in some cases.
                #
                if collection not in self.data:
                    # Add data collection
                    self.data[collection] = {}

                # If the data item key not exists.
                #
                # The original `change_data` function assumes it is existing,
                # but it is not in some cases.
                #
                if id not in self.data[collection]:
                    # Add data item
                    self.data[collection][id] = {}

                # For each data field changed
                for key, value in fields.items():
                    # Add to the data item
                    self.data[collection][id][key] = value

                # For each data field to be cleared
                for key in cleared:
                    # Delete from the data item
                    del self.data[collection][id][key]

            # Store original `change_data`.
            #
            # pylint: disable=protected-access
            CollectionData._orig_change_data = CollectionData.change_data
            # pylint: enable=protected-access

            # Replace original `change_data`
            CollectionData.change_data = change_data

    def build_identifier(self, username):
        """
        Create identifier object for given username.

        :param username: Rocket chat user name.

        :return: RocketChatUser instance.
        """
        # Create identifier object
        return RocketChatUser(username)

    def serve_forever(self):
        """
        Run the bot.

        Called by the Errbot framework.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- serve_forever -----')

        # Patch meteor client
        self._patch_meteor_client()

        # Get whether reconnect is enabled
        reconnect_enabled = self._get_bool_config(
            CONFIG_KEYS.RECONNECT_ENABLED,
            default=True,
        )

        try:
            # Loop
            while True:
                try:
                    # Run for once
                    self.serve_once()

                # If have error
                except Exception:
                    # Log message
                    self._log_error(('# ----- `serve_once` failed with error'
                                     ' -----\n{}').format(format_exc()))

                # If reconnect is enabled
                if reconnect_enabled:
                    # Get message
                    msg = ('# ----- Sleep before reconnect -----\n'
                           'Interval: {:.2f}').format(self._reconnection_delay)

                    # Log message
                    self._log_debug(msg)

                    # Sleep before reconnect
                    self._delay_reconnect()

                    # Log message
                    self._log_debug('# ----- Wake up to reconnect -----')

                    # Continue the loop
                    continue

                # If reconnect is not enabled
                else:
                    # Break the loop
                    break

        # If have `KeyboardInterrupt`
        except KeyboardInterrupt:
            # Do not treat as error
            pass

        # Always do
        finally:
            # Call `shutdown`
            self.shutdown()

        # Log message
        self._log_debug('# ===== serve_forever =====')

    def serve_once(self):
        """
        Run the bot until the connection is disconnected.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- serve_once -----')

        # Log message
        self._log_debug(('# ----- Create meteor client -----\n'
                         'SERVER_URI: {}').format(self._server_uri))

        # Create meteor client
        self._meteor_client = MeteorClient(
            self._server_uri,
            # Disable the meteor client's auto reconnect.
            # Let `serve_forever` handle reconnect.
            auto_reconnect=False,
        )

        # Log message
        self._log_debug('# ----- Hook meteor client callbacks -----')

        # 5DI82
        # Hook meteor client `connected` callback
        self._meteor_client.on('connected', self._meteor_connected_callback)

        # 2RAYF
        # Hook meteor client `changed` callback
        self._meteor_client.on('changed', self._meteor_changed_callback)

        # 4XIZB
        # Hook meteor client `added` callback
        self._meteor_client.on('added', self._meteor_added_callback)

        # 2JEIK
        # Hook meteor client `removed` callback
        self._meteor_client.on('removed', self._meteor_removed_callback)

        # 32TF2
        # Hook meteor client `failed` callback
        self._meteor_client.on('failed', self._meteor_failed_callback)

        # 5W6RX
        # Hook meteor client `reconnected` callback
        self._meteor_client.on('reconnected',
                               self._meteor_reconnected_callback)

        # 7MOJX
        # Hook meteor client `closed` callback
        self._meteor_client.on('closed', self._meteor_closed_callback)

        # Clear the event
        self._subscribing_done_event.clear()

        # Clear the event
        self._meteor_closed_event.clear()

        # Log message
        self._log_debug('# ----- Connect to meteor server -----')

        try:
            # Connect to meteor server.
            #
            # If the connecting succeeds, the meteor client's thread will call
            # `self._meteor_connected_callback` hooked at 5DI82. The login,
            # topic subscribing, and message handling are run in that thread.
            #
            # The main thread merely waits until the meteor client is closed,
            # meanwhile it calls heartbeat function at interval if specified.
            #
            self._meteor_client.connect()

        # If have error
        except:
            # Log message
            self._log_debug('# ----- Connecting failed -----')

            # Log message
            self._log_debug('# ----- Unhook meteor client callbacks -----')

            # Remove meteor client callbacks
            self._meteor_client.remove_all_listeners()

            # Remove meteor client reference
            self._meteor_client = None

            # The two events below should not have been set if the connecting
            # failed. Just in case.
            #
            # Clear the event
            self._subscribing_done_event.clear()

            # Clear the event
            self._meteor_closed_event.clear()

            # Raise the error
            raise

        # Get whether heartbeat is enabled
        heartbeat_enabled = self._get_bool_config(
            CONFIG_KEYS.HEARTBEAT_ENABLED)

        try:
            # Wait until the topic subscribing is done in another thread at
            # 5MS7A
            self._subscribing_done_event.wait()

            # If heartbeat is enabled
            if heartbeat_enabled:
                # Get heartbeat function
                heartbeat_func = self._get_config(CONFIG_KEYS.HEARTBEAT_FUNC)

                # Assert the function is callable
                assert callable(heartbeat_func), repr(heartbeat_func)

                # Get heartbeat interval
                heartbeat_interval = self._get_config(
                    CONFIG_KEYS.HEARTBEAT_INTERVAL,
                    default=10,
                )

                # Convert heartbeat interval to float
                heartbeat_interval = float(heartbeat_interval)

                # 65ZNO
                # Loop until the meteor client is disconnected
                while self._meteor_client.connected:
                    # Send heartbeat
                    heartbeat_func(self)

                    # Sleep before sending next heartbeat
                    time.sleep(heartbeat_interval)

            # 5W6XQ
            # Wait until the meteor client is closed and the `closed` callback
            # is called at 3DMYH
            self._meteor_closed_event.wait()

        # If have error
        except:
            # Close meteor client.
            #
            # This will cause `self._meteor_closed_callback` to be called,
            # which will set the `self._meteor_closed_event` below.
            #
            self._meteor_client.close()

            # See 5W6XQ
            self._meteor_closed_event.wait()

            # Raise the error
            raise

        # Always do
        finally:
            # Log message
            self._log_debug('# ----- Unhook meteor client callbacks -----')

            # Remove meteor client callbacks
            self._meteor_client.remove_all_listeners()

            # Remove meteor client reference.
            #
            # Do this before calling `callback_presence` below so that the
            # plugins will not be able to access the already closed client.
            #
            self._meteor_client = None

            # Log message
            self._log_debug('# ----- Call `callback_presence` -----')

            # Call `callback_presence`
            self.callback_presence(
                Presence(identifier=self.bot_identifier, status=OFFLINE))

            # Log message
            self._log_debug('# ----- Call `disconnect_callback` -----')

            # Call `disconnect_callback` to unload plugins
            self.disconnect_callback()

            # Clear the event
            self._subscribing_done_event.clear()

            # Clear the event
            self._meteor_closed_event.clear()

        # Log message
        self._log_debug('# ===== serve_once =====')

    def _meteor_connected_callback(self):
        """
        Callback called when the meteor client is connected.

        Hooked at 5DI82.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_connected_callback -----')

        # Log message
        self._log_debug(
            '# ----- Log in to meteor server -----\nUser: {}'.format(
                self._login_username))

        # Log in to meteor server
        self._meteor_client.login(
            user=self._login_username,
            password=self._login_password,
            # 2I0GP
            callback=self._meteor_login_callback,
        )

    def _meteor_login_callback(self, error_info, success_info):
        """
        Callback called when the meteor client has succeeded or failed login.

        Hooked at 2I0GP.

        :param error_info: Error info.

        :param success_info: Success info.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_login_callback -----')

        # If have error info
        if error_info is not None:
            # Get message
            msg = 'Login failed:\n{}'.format(pformat(error_info, width=1))

            # Log message
            self._log_debug(msg)

            # Close meteor client.
            # This will cause `_meteor_closed_callback` be called.
            self._meteor_client.close()

        # If not have error info
        else:
            # Get message
            msg = 'Login succeeded:\n{}'.format(pformat(success_info, width=1))

            # Log message
            self._log_debug(msg)

            # Subscribe to message events
            self._meteor_client.subscribe(
                # Topic name
                name='stream-room-messages',
                params=[
                    # All messages from rooms the rocket chat user has joined
                    '__my_messages__',
                    False,
                ],
                # 6BKIR
                callback=self._meteor_subscribe_callback,
            )

    def _meteor_subscribe_callback(self, error_info):
        """
        Callback called when the meteor client has succeeded or failed \
            subscribing.

        Hooked at 6BKIR.

        :param error_info: Error info.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_subscribe_callback -----')

        # If have error info
        if error_info is not None:
            # Get message
            msg = 'Subscribing failed:\n{}'.format(pformat(error_info,
                                                           width=1))

            # Log message
            self._log_debug(msg)

            # Close meteor client.
            # This will cause `self._meteor_closed_callback` to be called.
            self._meteor_client.close()

        # If not have error info
        else:
            # Log message
            self._log_debug('Subscribing succeeded.')

            # Log message
            self._log_debug('# ----- Call `connect_callback` -----')

            # Call `connect_callback` to load plugins.
            #
            # This is called in meteor client's thread.
            # Plugins should not assume they are loaded from the main thread.
            #
            self.connect_callback()

            # Log message
            self._log_debug('# ----- Call `callback_presence` -----')

            # Call `callback_presence`
            self.callback_presence(
                Presence(identifier=self.bot_identifier, status=ONLINE))

            # Log message
            self._log_debug(
                '# ----- Hook callback `_meteor_changed_callback` -----')

            # Reset reconnection count
            self.reset_reconnection_count()

            # 5MS7A
            # Set the topic subscribing is done
            self._subscribing_done_event.set()

    def _meteor_changed_callback(self, collection, id, fields, cleared):
        """
        Callback called when the meteor client received message.

        Hooked at 2RAYF.

        :param collection: Data collection key.

        :param id: Data item key.

        :param fields: Data fields changed.

        :param cleared: Data fields to be cleared.

        :return: None.
        """
        # If is message event
        if collection == 'stream-room-messages':
            # Get `args` value
            args = fields.get('args', None)

            # If `args` value is list
            if isinstance(args, list):
                # For each message info
                for msg_info in args:
                    # Get message
                    msg = msg_info.get('msg', None)

                    # If have message
                    if msg is not None:
                        # Get sender info
                        sender_info = msg_info['u']

                        # Get sender username
                        sender_username = sender_info['username']

                        # If the sender is not the bot itself
                        if sender_username != self._login_username:
                            # Create sender's identifier object
                            sender_identifier = self.build_identifier(
                                sender_username)

                            # Create extras info
                            extras = {
                                # 2QTGO
                                'msg_info': msg_info,
                            }

                            # Create received message object
                            msg_obj = Message(
                                body=msg,
                                frm=sender_identifier,
                                to=self.bot_identifier,
                                extras=extras,
                            )

                            # Log message
                            self._log_debug(
                                '# ----- Call `callback_message` -----')

                            # Call `callback_message` to dispatch the message
                            # to plugins
                            self.callback_message(msg_obj)

    def _meteor_added_callback(self, collection, id, fields):
        """
        Callback called when the meteor client emits `added` event.

        Hooked at 4XIZB.

        :param collection: Data collection key.

        :param id: Data item key.

        :param fields: Data fields.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_added_callback -----')

    def _meteor_removed_callback(self, collection, id):
        """
        Callback called when the meteor client emits `removed` event.

        Hooked at 2JEIK.

        :param collection: Data collection key.

        :param id: Data item key.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_removed_callback -----')

    def _meteor_failed_callback(self):
        """
        Callback called when the meteor client emits `failed` event.

        Hooked at 32TF2.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_failed_callback -----')

    def _meteor_reconnected_callback(self):
        """
        Callback called when the meteor client emits `reconnected` event.

        Hooked at 5W6RX.

        :return: None.
        """
        # Log message
        self._log_debug('# ----- _meteor_reconnected_callback -----')

    def _meteor_closed_callback(self, code, reason):
        """
        Callback called when the meteor client emits `closed` event.

        Hooked at 7MOJX.

        :param code: Close code.

        :param reason: Close reason.

        :return: None.
        """
        # Log message
        self._log_debug(
            '# ----- _meteor_closed_callback -----\nCode: {}\nReason: {}'.
            format(code, reason))

        # Set the topic subscribing is done
        self._subscribing_done_event.set()

        # 3DMYH
        # Set the meteor client's `closed` event is emitted
        self._meteor_closed_event.set()

    def build_reply(self, mess, text=None, private=False, threaded=False):
        """
        Create reply message object.

        Used by `self.send_simple_reply`.

        :param mess: The original message object.

        :param text: Reply message text.

        :param private: Whether the reply message is private.

        :return: Message object.
        """
        # Create reply message object
        reply = Message(
            body=text,
            frm=mess.to,
            to=mess.frm,
            extras={
                # 5QXGV
                # Store the original message object
                'orig_msg': mess
            })

        # Return reply message object
        return reply

    def prefix_groupchat_reply(self, message, identifier):
        """
        Add group chat prefix to the message.

        Used by `self.send` and `self.send_simple_reply`.

        :param message: Message object to send.

        :param identifier: The message receiver's identifier object.

        :return: None.
        """
        # Do nothing

    def send_rocketchat_message(self, params):
        """
        Send message to meteor server.

        :param params: RPC method `sendMessage`'s parameters.

        :return: None.
        """
        # If argument `params` is not list
        if not isinstance(params, list):
            # Put it in a list
            params = [params]

        # Send message to meteor server
        self._meteor_client.call(
            method='sendMessage',
            params=params,
        )

    def send_message(self, mess):
        """
        Send message to meteor server.

        Used by `self.split_and_send_message`. `self.split_and_send_message` is
        used by `self.send` and `self.send_simple_reply`.

        :param mess: Message object to send.

        :return: None.
        """
        # Call super method to dispatch to plugins
        super(AoikRocketChatErrbot, self).send_message(mess)

        # Get original message object.
        #
        # The key is set at 5QXGV and 3YRCT.
        #
        orig_msg = mess.extras['orig_msg']

        # Get original message info.
        #
        # The key is set at 2QTGO
        #
        msg_info = orig_msg.extras['msg_info']

        # Get room ID
        room_id = msg_info['rid']

        # Send message to meteor server
        self.send_rocketchat_message(params={
            'rid': room_id,
            'msg': mess.body,
        })

    def send(
        self,
        identifier,
        text,
        in_reply_to=None,
        groupchat_nick_reply=False,
    ):
        """
        Send message to meteor server.

        :param identifier: Receiver's identifier object.

        :param text: Message text to send.

        :param in_reply_to: Original message object.

        :param groupchat_nick_reply: Whether the message to send is group chat.

        `self.prefix_groupchat_reply` will be called to process the message if
        it is group chat.

        :return: None.
        """
        # If the identifier object is not Identifier instance
        if not isinstance(identifier, Identifier):
            # Get message
            error_msg = (
                'Argument `identifier` is not Identifier instance: {}').format(
                    repr(identifier))

            # Raise error
            raise ValueError(error_msg)

        # If the original message is not given
        if in_reply_to is None:
            # Get message
            error_msg = 'Argument `in_reply_to` must be given.'

            # Raise error
            raise ValueError(error_msg)

        # Create message object
        msg_obj = Message(
            body=text,
            frm=in_reply_to.to,
            to=identifier,
            extras={
                # 3YRCT
                # Store the original message object
                'orig_msg': in_reply_to,
            },
        )

        # Get group chat prefix from config
        group_chat_prefix = self.bot_config.GROUPCHAT_NICK_PREFIXED

        # If the receiver is a room
        if isinstance(identifier, Room):
            # If have group chat prefix,
            # or the message is group chat.
            if group_chat_prefix or groupchat_nick_reply:
                # Call `prefix_groupchat_reply` to process the message
                self.prefix_groupchat_reply(msg_obj, in_reply_to.frm)

        # Send the message
        self.split_and_send_message(msg_obj)

    def query_room(self, room):
        """
        Query room info. Not implemented.

        :param room: Room ID.

        :return: None.
        """
        # Return None
        return None

    def rooms(self):
        """
        Get room list. Not implemented.

        :return: Empty list.
        """
        # Return empty list
        return []

    def change_presence(self, status=ONLINE, message=''):
        """
예제 #7
0
파일: client.py 프로젝트: Foohx/kwak_cli
class Client:
    def __init__(self, username, password, ui):
        self.username = username
        self.password = password
        self.ui = ui
        self.ui.print_logo()
        self.now = mktime(datetime.now().timetuple())*1e3
        self.resume_token = ''
        self.client = MeteorClient('wss://kwak.io/websocket')
        self.client.connect()
        self.client.login(self.username, self.password,
            token=self.resume_token, callback=self.logged_in)

        self.hot_channels = []
        self.hot_channels_name = []
        self.all_channels_name = []
        self.current_channel = 'dev'
        self.client.call('getHotChannels', [], self.set_hot_channels_name)
        self.client.call('channelList', [], self.set_all_channels_name)

        self.client.on('connected', self.connected)
        self.client.on('added', self.added)

        """ Not usable right now """
        """self.client.update('users', {'username': self.username},
                           {'profile.chans': self.current_channel}, callback=self.update_callback)
        """

    """
    def update_callback(self, error, result):
        self.ui.chatbuffer_add("UPDATED !")
        self.ui.chatbuffer_add(str(error))
        self.ui.chatbuffer_add(str(result))
    """

    def set_hot_channels_name(self, error, result):
        if error:
            self.ui.chatbuffer_add(error)
            return
        self.hot_channels_name = result
        self.ui.chanlist = self.hot_channels_name
        self.ui.redraw_ui()

    def set_all_channels_name(self, error, result):
        if error:
            self.ui.chatbuffer_add(error)
            return
        self.all_channels_name = result

    def subscribe_to_channel(self, channel):
        self.current_channel = channel
        try:
            self.client.unsubscribe('messages')
        except:
            pass
        self.ui.chatbuffer_add('* LISTENING TO CHANNEL {}'.format(channel))
        self.ui.redraw_chathead(channel)
        self.client.subscribe('messages', [self.current_channel])

    def subscribe_to_users(self, channel):
        self.client.subscribe('users', [[channel]])

    def added(self, collection, id, fields):
        # only add new messages, not backlog
        if collection == 'messages' and fields['time'] > self.now:
            # fields : channel | time | text | user
            timestamp = int(fields['time']) // 1000
            timestamp = datetime.fromtimestamp(timestamp).strftime('%H:%M')
            self.ui.chatbuffer_add('{} {}: {}'.format(
                timestamp, fields['user'], fields['text']))
        elif collection == 'users':
            # fields : username | profile | color
            if len(fields['profile']) and bool(fields['profile'].get('online', False)):
                self.ui.userlist.append(fields['username'])
                self.ui.redraw_ui()

    def connected(self):
        self.ui.chatbuffer_add('* CONNECTED')

    def logged_in(self, error, data):
        if error:
            self.ui.chatbuffer_add('LOGIN ERROR {}'.format(error))
        else:
            self.resume_token = data['token']
            self.client.call('setOnline', [])
            self.ui.chatbuffer_add('* LOGGED IN')
            # add self.username in userlist when logged and fix duplicates names

    def logout(self):
        self.ui.chatbuffer_add('* BYE (LOVELY DUCK)')
예제 #8
0
def callback_function(error, result):
    if error:
        write_log('error', 'Error on method call: {}'.format(error))


client.on('connected', connected)
client.on('socket_closed', closed)
client.on('reconnected', reconnected)
client.on('subscribed', subscribed)
client.on('unsubscribed', unsubscribed)
client.on('logged_in', logged_in)
client.on('logged_out', logged_out)

client.connect()
client.logout()
client.login(configuration.USER, configuration.PASSWORD)

client.subscribe('configuration', callback=subscription_callback)
client.subscribe('queue', callback=subscription_callback)
client.subscribe('ingredients', callback=subscription_callback)
client.subscribe('cocktails', callback=subscription_callback)

sleep(1)

mixer_configuration = client.find_one('configuration',
                                      selector={"name": "mixer"})
mixer_status = client.find_one('configuration', selector={'name': 'status'})


def start_pump(valve):
    valvepin = configuration.VALVE_PINS[valve]
예제 #9
0
파일: ddp.py 프로젝트: CGreweling/peakaboo
class DDP(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('sussexlogin', 'room_name')
        self.vu_min = -70
        self.vu_range = 40
        self.do_vu = 0
        self.last_vu = None
        self.ip = socket.gethostbyname(socket.gethostname())
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.netreg_id = conf.get('ddp', 'netreg_id')
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False

        cam_available = conf.get(
            'sussexlogin',
            'cam_available') or cam_available
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)

        self.audiofaders = []
        faders = conf.get('ddp', 'audiofaders').split()
        for fader in faders:
            audiofader = {}
            fader = 'audiofader-' + fader
            audiofader['name'] = conf.get(fader, 'name')
            audiofader['display'] = conf.get(fader, 'display')
            audiofader['min'] = conf.get_int(fader, 'min')
            audiofader['max'] = conf.get_int(fader, 'max')
            audiofader['type'] = conf.get(fader, 'type')
            audiofader['setrec'] = conf.get_boolean(fader, 'setrec')
            audiofader['mute'] = conf.get_boolean(fader, 'mute')
            audiofader['unmute'] = conf.get_boolean(fader, 'unmute')
            audiofader['setlevel'] = conf.get_int(fader, 'setlevel')
            try:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
                self.audiofaders.append(audiofader)
            except Exception as e:
                logger.warn(e)
        fd, eventmask = self.audiofaders[0]['control'].polldescriptors()[0]
        self.watchid = gobject.io_add_watch(fd, eventmask, self.mixer_changed)

        dispatcher.connect('galicaster-init', self.on_init)
        dispatcher.connect('update-rec-vumeter', self.vumeter)
        dispatcher.connect('galicaster-notify-timer-short', self.heartbeat)
        dispatcher.connect('start-before', self.on_start_recording)
        dispatcher.connect('restart-preview', self.on_stop_recording)
        dispatcher.connect('update-rec-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(
                    collection,
                    query,
                    update,
                    callback=self.update_callback)
            except Exception:
                logger.warn(
                    "Error updating document "
                    "{collection: %s, query: %s, update: %s}" %
                    (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(
                    collection,
                    document,
                    callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = context.get_state().profile.name
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$set': {
                    'currentMediaPackage': self.currentMediaPackage,
                    'currentProfile': self.currentProfile,
                    'recording': self.recording
                }
            })

    def on_stop_recording(self, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$unset': {
                    'currentMediaPackage': '',
                    'currentProfile': ''
                }, '$set': {
                    'recording': self.recording
                }
            })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0):
        worker = Thread(target=self._update_images, args=(delay,))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}
        audio_devices = ['audiotest', 'autoaudio', 'pulse']
        for track in context.get_state().profile.tracks:
            if track.device not in audio_devices:
                file = os.path.join('/tmp', track.file + '.jpg')
                try:
                    if(os.path.getctime(file) > time.time() - 3):
                        files[track.flavor] = (track.flavor + '.jpg',
                                               open(file, 'rb'),
                                               'image/jpeg')
                except Exception:
                    logger.warn("Unable to check date of or open file (%s)"
                                % file)
        im = ImageGrab.grab(bbox=(10, 10, 1280, 720), backend='imagemagick')
        im.thumbnail((640, 360))
        output = cStringIO.StringIO()
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format="JPEG")
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" %
                (self._http_host, self.id), files=files, auth=(
                    self._user, self._password))
        except Exception:
            logger.warn('Unable to post images')

    def mixer_changed(self, source=None, condition=None, reopen=True):
        if reopen:
            for audiofader in self.audiofaders:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
        self.update_audio()
        return True

    def vumeter(self, element, data):
        if self.do_vu == 0:
            if data == "Inf":
                data = 0
            else:
                if data < -self.vu_range:
                    data = -self.vu_range
                elif data > 0:
                    data = 0
            data = int(((data + self.vu_range) / float(self.vu_range)) * 100)
            if data != self.last_vu:
                update = {'vumeter': data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = data
        self.do_vu = (self.do_vu + 1) % 20

    def on_rec_status_update(self, element, data):
        is_paused = data == 'Paused'
        if is_paused:
            self.update_images(.75)
        if self.paused != is_paused:
            self.update(
                'rooms', {
                    '_id': self.id}, {
                    '$set': {
                        'paused': is_paused}})
            self.paused = is_paused
        if data == '  Recording  ':
            subprocess.call(['killall', 'maliit-server'])
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get_repository().get(id)
        line = mp.metadata_episode.copy()
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # Does series_title need sanitising as well as duration?
        created = mp.getDate()
        line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if(subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            stream_key = uuid.uuid4().get_hex()

            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'netregId': self.netreg_id,
                'inputs': self.inputs(),
                'stream': {
                    'port': self._audiostream_port,
                    'key': stream_key
                },
                'galicasterVersion': galicaster.__version__
            }
            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {
                    '$set': data
                }

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                audio = self.read_audio_settings()
                data['_id'] = self.id
                data['audio'] = audio
                self.insert('rooms', data)

    def inputs(self):
        inputs = {
            'presentations': ['Presentation']
        }
        inputs['cameras'] = []
        labels = conf.get('sussexlogin', 'matrix_cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(
                cam_labels) else "Camera %d" % (i + 1)
            inputs['cameras'].append(label)
        return inputs

    def set_audio(self, fields):
        faders = fields.get('audio')
        if faders:
            for fader in faders:
                mixer = None
                level = fader.get('level')
                for audiofader in self.audiofaders:
                    if audiofader['name'] == fader['name']:
                        mixer = audiofader['control']
                        break
                if mixer:
                    l, r = mixer.getvolume(fader['type'])
                    if level >= 0 and l != level:
                        mixer.setvolume(level, 0, fader['type'])
                        mixer.setvolume(level, 1, fader['type'])
            if self.store_audio:
                # Relies on no password sudo access for current user to alsactl
                subprocess.call(['sudo', 'alsactl', 'store'])

    def on_added(self, collection, id, fields):
        self.set_audio(fields)
        self.update_audio()

    def on_changed(self, collection, id, fields, cleared):
        self.set_audio(fields)
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])
        if context.get_state().is_recording != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        self.paused = new_status
        dispatcher.emit("toggle-pause-rec")

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {'user_name': meta.get('creator', ''),
                    'user_id': meta.get('rightsHolder', '')}
            title = meta.get('title', 'Unknown')
            dispatcher.emit('sussexlogin-record',
                            (user, title, series, profile))
        else:
            dispatcher.emit("stop-record", '')

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe(
                'GalicasterControl',
                params=[
                    self.id],
                callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def update_audio(self):
        me = self.client.find_one('rooms')
        audio = self.read_audio_settings()
        update = False
        if me:
            mAudio = me.get('audio')
            mAudioNames = [x['name'] for x in mAudio]
            audioNames = [x['name'] for x in audio]
            if set(mAudioNames) != set(audioNames):
                update = True
            if not update:
                for key, fader in enumerate(audio):
                    if mAudio[key].get('level') != fader.get('level'):
                        update = True
            if update:
                self.update(
                    'rooms', {
                        '_id': self.id}, {
                        '$set': {
                            'audio': audio}})

    def read_audio_settings(self):
        audio_settings = []
        for audiofader in self.audiofaders:
            if audiofader['display']:
                audio_settings.append(
                    self.control_values(audiofader)
                )
            # ensure fixed values
            mixer = audiofader['control']
            if audiofader['setrec']:
                mixer.setrec(1)
            if audiofader['mute']:
                mixer.setmute(1)
            if audiofader['unmute']:
                mixer.setmute(0)
            if audiofader['setlevel'] >= 0:
                mixer.setvolume(audiofader['setlevel'], 0, audiofader['type'])
                if 'Joined Playback Volume' not in mixer.volumecap():
                    mixer.setvolume(audiofader['setlevel'],
                                    1, audiofader['type'])
        return audio_settings

    def control_values(self, audiofader):
        controls = {}
        left, right = audiofader['control'].getvolume(audiofader['type'])
        controls['min'] = audiofader['min']
        controls['max'] = audiofader['max']
        controls['level'] = left
        controls['type'] = audiofader['type']
        controls['name'] = audiofader['name']
        controls['display'] = audiofader['display']
        return controls

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) is not None
예제 #10
0
파일: ddp.py 프로젝트: UoM-Podcast/peakaboo
class DDP(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('ddp', 'room_name')
        self.vu_min = -50
        self.vu_range = 50
        self.vu_data = 0
        self.last_vu = None
        self.ip = conf.get('ingest', 'address')
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.screenshot_file = conf.get('ddp', 'existing_screenshot')
        self.high_quality = conf.get_boolean('ddp', 'hq_snapshot')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False
        screen = Gdk.Screen.get_default()
        self._screen_width = screen.get_width()
        self._screen_height = screen.get_height()
        self.cardindex = None

        cam_available = conf.get('ddp', 'cam_available') or 0
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)
        # Getting audiostream params. either using existing audiostreaming server like icecast or the audiostream plugin
        if conf.get('ddp', 'existing_stream_host'):
            self._stream_host = conf.get('ddp', 'existing_stream_host')
        else:
            self._stream_host = self.ip

        if conf.get_int('ddp', 'existing_stream_port'):
            self._audiostream_port = conf.get_int('ddp',
                                                  'existing_stream_port')
        else:
            self._audiostream_port = conf.get_int('audiostream',
                                                  'port') or 31337

        if conf.get('ddp', 'existing_stream_key'):
            self.stream_key = conf.get('ddp', 'existing_stream_key')
        else:
            self.stream_key = uuid.uuid4().get_hex()

        if conf.get('ddp', 'extra_params'):
            self.extra_params_list = conf.get('ddp', 'extra_params').split(';')
        else:
            self.extra_params_list = []
        logger.info(
            'audiostream URI: {}'.format('http://' + self._stream_host + ':' +
                                         str(self._audiostream_port) + '/' +
                                         self.stream_key))

        dispatcher.connect('init', self.on_init)
        dispatcher.connect('recorder-vumeter', self.vumeter)
        dispatcher.connect('timer-short', self.update_vu)
        dispatcher.connect('timer-short', self.heartbeat)
        dispatcher.connect('recorder-started', self.on_start_recording)
        dispatcher.connect('recorder-stopped', self.on_stop_recording)
        dispatcher.connect('recorder-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(collection,
                                   query,
                                   update,
                                   callback=self.update_callback)
            except Exception:
                logger.warn("Error updating document "
                            "{collection: %s, query: %s, update: %s}" %
                            (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(collection,
                                   document,
                                   callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = conf.get_current_profile().name
        self.update('rooms', {'_id': self.id}, {
            '$set': {
                'currentMediaPackage': self.currentMediaPackage,
                'currentProfile': self.currentProfile,
                'recording': self.recording
            }
        })

    def on_stop_recording(self, mpid, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update('rooms', {'_id': self.id}, {
            '$unset': {
                'currentMediaPackage': '',
                'currentProfile': ''
            },
            '$set': {
                'recording': self.recording
            }
        })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0.0):
        worker = Thread(target=self._update_images, args=(delay, ))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}

        if not self.screenshot_file:
            # take a screenshot with pyscreenshot
            im = ImageGrab.grab(bbox=(0, 0, self._screen_width,
                                      self._screen_height),
                                backend='imagemagick')
        else:
            try:
                # used if screenshot already exists
                im = Image.open(self.screenshot_file)
            except IOError as e:
                logger.warn("Unable to open screenshot file {0}".format(
                    self.screenshot_file))
                return
        output = cStringIO.StringIO()
        image_format = 'JPEG'
        if not self.high_quality:
            im.thumbnail((640, 360), Image.ANTIALIAS)
        else:
            image_format = 'PNG'

        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format=image_format
                )  # to reduce jpeg size use param: optimize=True
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" % (self._http_host, self.id),
                files=files,
                auth=(self._user, self._password
                      ))  # to ignore ssl verification, use param: verify=False
        except Exception:
            logger.warn('Unable to post images')

    def vumeter(self, element, data, data_chan2, vu_bool):
        if data == "Inf":
            data = 0
        else:
            if data < -self.vu_range:
                data = -self.vu_range
            elif data > 0:
                data = 0
        self.vu_data = int(
            ((data + self.vu_range) / float(self.vu_range)) * 100)

    def update_vu(self, element):
        if self.vu_data != self.last_vu:
            update = {'vumeter': self.vu_data}
            self.update('rooms', {'_id': self.id}, {'$set': update})
            self.last_vu = self.vu_data

    def on_rec_status_update(self, element, data):
        if data == 'paused':
            is_paused = True
        else:
            is_paused = False
        if is_paused:
            self.update_images(.75)
        if self.paused == is_paused:
            self.update('rooms', {'_id': self.id},
                        {'$set': {
                            'paused': is_paused
                        }})
            self.paused = is_paused
        if data == 'recording':
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get('recorder').current_mediapackage
        line = mp.metadata_episode
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # FIXME Does series_title need sanitising as well as duration?
        created = mp.getDate()
        # line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        # return line
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if (subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'inputs': self.inputs(),
                'stream': {
                    'host': self._stream_host,
                    'port': self._audiostream_port,
                    'key': self.stream_key
                }
            }
            # Parse extra Meteor Mongodb collection elements and append
            for params in self.extra_params_list:
                param = params.split(':')
                data[param[0]] = param[1]

            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {'$set': data}

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                data['_id'] = self.id
                self.insert('rooms', data)

    def inputs(self):
        inputs = {'presentations': ['Presentation']}
        inputs['cameras'] = []
        labels = conf.get('ddp', 'cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(cam_labels) else "Camera %d" % (
                i + 1)
            inputs['cameras'].append(label)
        return inputs

    def on_added(self, collection, id, fields):
        pass

    def on_changed(self, collection, id, fields, cleared):
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])

        if context.get('recorder').is_recording() != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        if not self.paused:
            self.paused = new_status
            context.get('recorder').pause()
        else:
            self.paused = False
            context.get('recorder').resume()

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            # FIXME: Metadata isn't passed to recorder
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {
                'user_name': meta.get('creator', ''),
                'user_id': meta.get('rightsHolder', '')
            }
            title = meta.get('title', 'Unknown')
            context.get('recorder').record()
        else:
            context.get('recorder').stop()

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe('GalicasterControl',
                                  params=[self.id],
                                  callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) != None
예제 #11
0
파일: ddp.py 프로젝트: UoM-Podcast/peakaboo
class DDP(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('sussexlogin', 'room_name')
        self.vu_min = -70
        self.vu_range = 40
        self.do_vu = 0
        self.last_vu = None
        self.ip = socket.gethostbyname(socket.gethostname())
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.netreg_id = conf.get('ddp', 'netreg_id')
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False

        cam_available = conf.get('sussexlogin',
                                 'cam_available') or cam_available
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)

        self.audiofaders = []
        faders = conf.get('ddp', 'audiofaders').split()
        for fader in faders:
            audiofader = {}
            fader = 'audiofader-' + fader
            audiofader['name'] = conf.get(fader, 'name')
            audiofader['display'] = conf.get(fader, 'display')
            audiofader['min'] = conf.get_int(fader, 'min')
            audiofader['max'] = conf.get_int(fader, 'max')
            audiofader['type'] = conf.get(fader, 'type')
            audiofader['setrec'] = conf.get_boolean(fader, 'setrec')
            audiofader['mute'] = conf.get_boolean(fader, 'mute')
            audiofader['unmute'] = conf.get_boolean(fader, 'unmute')
            audiofader['setlevel'] = conf.get_int(fader, 'setlevel')
            try:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
                self.audiofaders.append(audiofader)
            except Exception as e:
                logger.warn(e)
        fd, eventmask = self.audiofaders[0]['control'].polldescriptors()[0]
        self.watchid = gobject.io_add_watch(fd, eventmask, self.mixer_changed)

        dispatcher.connect('galicaster-init', self.on_init)
        dispatcher.connect('update-rec-vumeter', self.vumeter)
        dispatcher.connect('galicaster-notify-timer-short', self.heartbeat)
        dispatcher.connect('start-before', self.on_start_recording)
        dispatcher.connect('restart-preview', self.on_stop_recording)
        dispatcher.connect('update-rec-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(collection,
                                   query,
                                   update,
                                   callback=self.update_callback)
            except Exception:
                logger.warn("Error updating document "
                            "{collection: %s, query: %s, update: %s}" %
                            (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(collection,
                                   document,
                                   callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = context.get_state().profile.name
        self.update('rooms', {'_id': self.id}, {
            '$set': {
                'currentMediaPackage': self.currentMediaPackage,
                'currentProfile': self.currentProfile,
                'recording': self.recording
            }
        })

    def on_stop_recording(self, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update('rooms', {'_id': self.id}, {
            '$unset': {
                'currentMediaPackage': '',
                'currentProfile': ''
            },
            '$set': {
                'recording': self.recording
            }
        })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0):
        worker = Thread(target=self._update_images, args=(delay, ))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}
        audio_devices = ['audiotest', 'autoaudio', 'pulse']
        for track in context.get_state().profile.tracks:
            if track.device not in audio_devices:
                file = os.path.join('/tmp', track.file + '.jpg')
                try:
                    if (os.path.getctime(file) > time.time() - 3):
                        files[track.flavor] = (track.flavor + '.jpg',
                                               open(file, 'rb'), 'image/jpeg')
                except Exception:
                    logger.warn("Unable to check date of or open file (%s)" %
                                file)
        im = ImageGrab.grab(bbox=(10, 10, 1280, 720), backend='imagemagick')
        im.thumbnail((640, 360))
        output = cStringIO.StringIO()
        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format="JPEG")
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post("%s/image/%s" % (self._http_host, self.id),
                          files=files,
                          auth=(self._user, self._password))
        except Exception:
            logger.warn('Unable to post images')

    def mixer_changed(self, source=None, condition=None, reopen=True):
        if reopen:
            for audiofader in self.audiofaders:
                audiofader['control'] = alsaaudio.Mixer(
                    control=audiofader['name'])
        self.update_audio()
        return True

    def vumeter(self, element, data):
        if self.do_vu == 0:
            if data == "Inf":
                data = 0
            else:
                if data < -self.vu_range:
                    data = -self.vu_range
                elif data > 0:
                    data = 0
            data = int(((data + self.vu_range) / float(self.vu_range)) * 100)
            if data != self.last_vu:
                update = {'vumeter': data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = data
        self.do_vu = (self.do_vu + 1) % 20

    def on_rec_status_update(self, element, data):
        is_paused = data == 'Paused'
        if is_paused:
            self.update_images(.75)
        if self.paused != is_paused:
            self.update('rooms', {'_id': self.id},
                        {'$set': {
                            'paused': is_paused
                        }})
            self.paused = is_paused
        if data == '  Recording  ':
            subprocess.call(['killall', 'maliit-server'])
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get_repository().get(id)
        line = mp.metadata_episode.copy()
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # Does series_title need sanitising as well as duration?
        created = mp.getDate()
        line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if (subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            stream_key = uuid.uuid4().get_hex()

            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'netregId': self.netreg_id,
                'inputs': self.inputs(),
                'stream': {
                    'port': self._audiostream_port,
                    'key': stream_key
                },
                'galicasterVersion': galicaster.__version__
            }
            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {'$set': data}

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                audio = self.read_audio_settings()
                data['_id'] = self.id
                data['audio'] = audio
                self.insert('rooms', data)

    def inputs(self):
        inputs = {'presentations': ['Presentation']}
        inputs['cameras'] = []
        labels = conf.get('sussexlogin', 'matrix_cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(cam_labels) else "Camera %d" % (
                i + 1)
            inputs['cameras'].append(label)
        return inputs

    def set_audio(self, fields):
        faders = fields.get('audio')
        if faders:
            for fader in faders:
                mixer = None
                level = fader.get('level')
                for audiofader in self.audiofaders:
                    if audiofader['name'] == fader['name']:
                        mixer = audiofader['control']
                        break
                if mixer:
                    l, r = mixer.getvolume(fader['type'])
                    if level >= 0 and l != level:
                        mixer.setvolume(level, 0, fader['type'])
                        mixer.setvolume(level, 1, fader['type'])
            if self.store_audio:
                # Relies on no password sudo access for current user to alsactl
                subprocess.call(['sudo', 'alsactl', 'store'])

    def on_added(self, collection, id, fields):
        self.set_audio(fields)
        self.update_audio()

    def on_changed(self, collection, id, fields, cleared):
        self.set_audio(fields)
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])
        if context.get_state().is_recording != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        self.paused = new_status
        dispatcher.emit("toggle-pause-rec")

    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {
                'user_name': meta.get('creator', ''),
                'user_id': meta.get('rightsHolder', '')
            }
            title = meta.get('title', 'Unknown')
            dispatcher.emit('sussexlogin-record',
                            (user, title, series, profile))
        else:
            dispatcher.emit("stop-record", '')

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe('GalicasterControl',
                                  params=[self.id],
                                  callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def update_audio(self):
        me = self.client.find_one('rooms')
        audio = self.read_audio_settings()
        update = False
        if me:
            mAudio = me.get('audio')
            mAudioNames = [x['name'] for x in mAudio]
            audioNames = [x['name'] for x in audio]
            if set(mAudioNames) != set(audioNames):
                update = True
            if not update:
                for key, fader in enumerate(audio):
                    if mAudio[key].get('level') != fader.get('level'):
                        update = True
            if update:
                self.update('rooms', {'_id': self.id},
                            {'$set': {
                                'audio': audio
                            }})

    def read_audio_settings(self):
        audio_settings = []
        for audiofader in self.audiofaders:
            if audiofader['display']:
                audio_settings.append(self.control_values(audiofader))
            # ensure fixed values
            mixer = audiofader['control']
            if audiofader['setrec']:
                mixer.setrec(1)
            if audiofader['mute']:
                mixer.setmute(1)
            if audiofader['unmute']:
                mixer.setmute(0)
            if audiofader['setlevel'] >= 0:
                mixer.setvolume(audiofader['setlevel'], 0, audiofader['type'])
                if 'Joined Playback Volume' not in mixer.volumecap():
                    mixer.setvolume(audiofader['setlevel'], 1,
                                    audiofader['type'])
        return audio_settings

    def control_values(self, audiofader):
        controls = {}
        left, right = audiofader['control'].getvolume(audiofader['type'])
        controls['min'] = audiofader['min']
        controls['max'] = audiofader['max']
        controls['level'] = left
        controls['type'] = audiofader['type']
        controls['name'] = audiofader['name']
        controls['display'] = audiofader['display']
        return controls

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) is not None
예제 #12
0
class MyProgram:
    def __init__(self):
        slackLog("Gate scanner started.")
        self.parents= {}
        #load parents
        self.client = MeteorClient('ws://chloe.asianhope.org:8080/websocket',debug=False)
        self.client.connect()
        self.client.login(METEOR_USERNAME,METEOR_PASSWORD)
        self.client.subscribe('cards')
        self.client.subscribe('scans')
        time.sleep(3) #give it some time to finish loading everything
        self.all_cards = self.client.find('cards')
        slackLog("Pulled records for: "+str(len(self.all_cards))+" cards")

        for card in self.all_cards:
            try:
                barcode = card['barcode']
                name = card['name']
                cardtype = card['type']
                expires = card['expires']
                profile = card.get('profile',barcode+".JPG") #default picture
                associations = card['associations']
                self.parents.update({barcode:card})

            except KeyError:
                slackLog(barcode+' has missing data',delay=True)

                pass
        # load style css for template
        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        # connect to glade teamplate
        self.gladefile = "cardmanager.glade"
        self.glade = Gtk.Builder()
        self.glade.add_from_file(self.gladefile)
        self.glade.connect_signals(self)

        #get window from glade
        self.app_window=self.glade.get_object("main_window") # Window Name in GLADE
        self.app_window.fullscreen()
        # quit app
        self.app_window.connect("delete-event",Gtk.main_quit)

        #change color of window??
        #self.green = gtk.gdk.color_parse('green')
        #self.black = gtk.gdk.color_parse('black')
        #self.app_window.modify_bg(gtk.STATE_NORMAL,self.black)

        # get objects from glade
        self.header = self.glade.get_object("header")
        self.header_context = self.header.get_style_context()
        self.header_title = self.glade.get_object("header_title")
        self.parent_image = self.glade.get_object("img_parent")
        self.child_container = self.glade.get_object("grid1")
        self.button_search = self.glade.get_object("btn_search")
        self.entry = self.glade.get_object("search_input")
        self.pname = self.glade.get_object("lbl_pname")
        self.pbarcode = self.glade.get_object("lbl_pbarcode")
        self.pexpires = self.glade.get_object("lbl_pexpires")
        self.error_message = self.glade.get_object("lbl_error_message")

        #add event to button_search
        self.button_search.connect("clicked", self.search_button_clicked, "3")


        # display children images
        pixbuf = Pixbuf.new_from_file("static/logo.png")
        scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)

        self.pickup_students = ['0']*9 #seed the list with the size we want
        for i in range(0,9):
            self.pickup_students[i] = Gtk.Image()
            self.pickup_students[i].set_from_pixbuf(scaled_buf)

        self.label=Gtk.Table(3,3,True)
        self.label.attach(self.pickup_students[0],0,1,0,1)
        self.label.attach(self.pickup_students[1],1,2,0,1)
        self.label.attach(self.pickup_students[2],2,3,0,1)

        self.label.attach(self.pickup_students[3],0,1,1,2)
        self.label.attach(self.pickup_students[4],1,2,1,2)
        self.label.attach(self.pickup_students[5],2,3,1,2)
        self.label.attach(self.pickup_students[6],0,1,2,3)
        self.label.attach(self.pickup_students[7],1,2,2,3)
        self.label.attach(self.pickup_students[8],2,3,2,3)
        self.label.set_col_spacings(10)
        self.label.set_row_spacings(10)
        # add lebel of image to container in glade
        self.child_container.add(self.label)

        # display parent picture
        pixbuf = Pixbuf.new_from_file("static/logo.png")
        scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
        self.parent_image.set_from_pixbuf(scaled_buf)


        # self.button_search.can_default(True)
        # self.button_search.grab_default()
        self.entry.set_activates_default(True)
        self.app_window.set_focus(self.entry)

        self.error_message.set_text("")
        self.app_window.show_all()
        return

    def search_button_clicked(self, widget, data=None):
        associations = []

        self.error_message.set_text("")
        # remove classes in header
        header_class_list = self.header_context.list_classes()
        for class_name in header_class_list:
            self.header_context.remove_class(class_name)

        for i in range(0,9):
            #make sure all pictures are reset
            pixbuf = Pixbuf.new_from_file("static/logo.png")
            scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
            self.pickup_students[i].set_from_pixbuf(scaled_buf)

        #grab pid
        pid = self.entry.get_text()
        slackLog('Scanned card: '+pid,delay=True)

        #do a lookup for the name
        try:
            #get parent information
            #parent_card = self.client.find_one('cards', selector={'barcode': pid})
            parent_card = self.parents[pid]
            slackLog('```'+str(parent_card)+'```',delay=True)

            if not parent_card:
                self.header_title.set_text("Invalid Card!")
                self.header_context.add_class('header_invalid_card')
                self.pname.set_text("Card Not Found!")
                self.pbarcode.set_text("XXXX")
                self.pexpires.set_text("xxxx-xx-xx")
                pixbuf = Pixbuf.new_from_file("static/NA.JPG")
                scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                self.parent_image.set_from_pixbuf(scaled_buf)
            else:
                pname = parent_card.get('name', pid)
                parent_picture = parent_card.get('profile',pid+".JPG")
                expires = parent_card.get('expires',"Expiry not set")
                barcode = parent_card.get('barcode',"Barcode not set")
                associations = parent_card.get('associations',[])

                # if card expired
                if expires < date.today().isoformat():
                    associations = []
                    self.header_title.set_text("Card has expired!")
                    self.header_context.add_class('header_expired')
                else:
                    def getScanCallbackFunction(error, result):
                        # if cannot get scan, display error message
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                            return
                        else:
                            # if card no scan in, add new scan
                            if result == None:
                                # scan in
                                action = 'Security Scan'
                                value = 0.00
                                products = []
                                user = METEOR_USERNAME
                                self.client.call('scanIn',[pid,action,value,products,user],scanInCallbackFunction)
                            # if card already scan in, update scan
                            else:
                                # scan out
                                scan_id = result['_id']
                                self.client.call('scanOut',[scan_id],scanOutCallbackFunction)

                    def scanInCallbackFunction(error,result):
                        # to check if card scan-in success or error
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                        else:
                            self.header_title.set_text("Scan-in")
                            self.header_context.add_class('header_scan_in')
                    def scanOutCallbackFunction(error,result):
                        # to check if card scan-out success or error
                        if error:
                            self.header_title.set_text('Scan failed!')
                            self.error_message.set_text(error['message'])
                            self.header_context.add_class('header_invalid_card')
                        else:
                            self.header_title.set_text("Scan-out")
                            self.header_context.add_class('header_scan_out')

                    # get scan to check if scan in or scan out
                    self.client.call('get_scan',[pid],getScanCallbackFunction)

                self.pname.set_text(pname)
                self.pbarcode.set_text(barcode)
                self.pexpires.set_text(expires)
                # load picture
                try:
                    slackLog('loading parent picture: '+str(pid),delay=True)
                    fetchPhotosByID(parent_picture)
                    pixbuf = Pixbuf.new_from_file("resource/"+parent_picture)
                    scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                    self.parent_image.set_from_pixbuf(scaled_buf)
                except Exception as inst:
                    slackLog("No parent picture for: "+pid,delay=True)
                    pixbuf = Pixbuf.new_from_file("static/unknown.jpg")
                    scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
                    self.parent_image.set_from_pixbuf(scaled_buf)

        except KeyError:
            slackLog('Scanned card: '+pid+' could not be found',delay=True)
            #display an error
            self.header_title.set_text("Invalid Card!")
            self.header_context.add_class('header_invalid_card')
            self.pname.set_text("Card Not Found!")
            self.pbarcode.set_text("XXXX")
            self.pexpires.set_text("xxxx-xx-xx")

            pixbuf = Pixbuf.new_from_file("static/NA.JPG")
            scaled_buf = pixbuf.scale_simple(PARENT_DIMENSIONS_X,PARENT_DIMENSIONS_Y,InterpType.BILINEAR)
            self.parent_image.set_from_pixbuf(scaled_buf)

            #reset everything
            self.entry.set_text('')
            self.app_window.set_focus(self.entry)
            self.app_window.show()


        #try and load the studnts starting after the parents name
        i = 0
        if(len(associations)):

            pool = ThreadPool(len(associations))
            results = pool.map(fetchPhotosByID,associations)
        for sid in associations:
            #if the student picture exists locally, load it
            try:
                pixbuf = Pixbuf.new_from_file("resource/"+sid+".JPG")
                scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
                self.pickup_students[i].set_from_pixbuf(scaled_buf)
            #if not, load the NA picture to indicate a student w/o a picture
            except:
                print("Unexpected error:```")
                print sys.exc_info()[0]
                pixbuf = Pixbuf.new_from_file("static/NA.JPG")
                scaled_buf = pixbuf.scale_simple(CHILD_DIMENSIONS_X,CHILD_DIMENSIONS_Y,InterpType.BILINEAR)
                self.pickup_students[i].set_from_pixbuf(scaled_buf)
            i+=1



        #clear entry box and reset focus
        self.entry.set_text('')
        self.app_window.set_focus(self.entry)
        self.app_window.show()
예제 #13
0
class Client:
    def __init__(self, username, password, ui):
        self.username = username
        self.password = password
        self.ui = ui
        self.now = mktime(datetime.now().timetuple())*1e3
        self.resume_token = ''
        self.client = MeteorClient('wss://kwak.io/websocket')
        self.client.connect()
        self.client.login(self.username, self.password,
            token=self.resume_token, callback=self.logged_in)

        self.hot_channels = []
        self.hot_channels_name = []
        self.all_channels_name = []
        self.current_channel = 'dev'
        self.client.call('getHotChannels', [], self.set_hot_channels_name)
        self.client.call('channelList', [], self.set_all_channels_name)

        self.client.on('connected', self.connected)
        self.client.on('added', self.added)

    def set_hot_channels_name(self, error, result):
        if error:
            self.ui.chatbuffer_add(error)
            return
        self.hot_channels_name = result

    def set_all_channels_name(self, error, result):
        if error:
            self.ui.chatbuffer_add(error)
            return
        self.all_channels_name = result

    def subscribe_to_channel(self, channel):
        self.current_channel = channel
        try:
            self.client.unsubscribe('messages')
        except:
            pass
        self.ui.chatbuffer_add('* LISTENING TO CHANNEL {}'.format(channel))
        self.client.subscribe('messages', [self.current_channel])

    def subscribe_to_users(self, channel):
        self.client.subscribe('users', [channel])

    def added(self, collection, id, fields):
        # only add new messages, not backlog
        if collection == 'messages' and fields['time'] > self.now:
            # fields : channel | time | text | user
            self.ui.chatbuffer_add('{}\t{}: {}'.format(fields['time'], fields['user'], fields['text']))
        elif collection == 'users':
            # fields : username | profile | color
            if len(fields['profile']) > 0 and bool(fields['profile']['online']) == True:
                self.ui.userlist.append(fields['username'])
                self.ui.redraw_userlist()

    def connected(self):
        self.ui.chatbuffer_add('* CONNECTED')

    def logged_in(self, error, data):
        if error:
            self.ui.chatbuffer_add('LOGIN ERROR {}'.format(error))
        else:
            self.resume_token = data['token']
            self.client.call('setOnline', [])
            self.ui.chatbuffer_add('* LOGGED IN')
            
    def logout(self):
        self.ui.chatbuffer_add('* BYE (LOVELY DUCK)')
예제 #14
0
class DDP(Thread):

    def __init__(self):
        Thread.__init__(self)
        self.meteor = conf.get('ddp', 'meteor')

        self.client = MeteorClient(self.meteor, debug=False)
        self.client.on('added', self.on_added)
        self.client.on('changed', self.on_changed)
        self.client.on('subscribed', self.on_subscribed)
        self.client.on('connected', self.on_connected)
        self.client.on('removed', self.on_removed)
        self.client.on('closed', self.on_closed)
        self.client.on('logged_in', self.on_logged_in)

        self.displayName = conf.get('ddp', 'room_name')
        self.vu_min = -50
        self.vu_range = 50
        self.vu_data = 0
        self.last_vu = None
        self.ip = conf.get('ingest', 'address')
        self.id = conf.get('ingest', 'hostname')
        self._user = conf.get('ddp', 'user')
        self._password = conf.get('ddp', 'password')
        self._http_host = conf.get('ddp', 'http_host')
        self._audiostream_port = conf.get('audiostream', 'port') or 31337
        self.store_audio = conf.get_boolean('ddp', 'store_audio')
        self.screenshot_file = conf.get('ddp', 'existing_screenshot')
        self.high_quality = conf.get_boolean('ddp', 'hq_snapshot')
        self.paused = False
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.has_disconnected = False
        screen = Gdk.Screen.get_default()
        self._screen_width = screen.get_width()
        self._screen_height = screen.get_height()
        self.cardindex = None

        cam_available = conf.get(
            'ddp',
            'cam_available') or 0
        if cam_available in ('True', 'true', True, '1', 1):
            self.cam_available = 1
        elif cam_available in ('False', 'false', False, '0', 0):
            self.cam_available = 0
        else:
            self.cam_available = int(cam_available)
        # Getting audiostream params. either using existing audiostreaming server like icecast or the audiostream plugin
        if conf.get('ddp', 'existing_stream_host'):
            self._stream_host = conf.get('ddp', 'existing_stream_host')
        else:
            self._stream_host = self.ip

        if conf.get_int('ddp', 'existing_stream_port'):
            self._audiostream_port = conf.get_int('ddp', 'existing_stream_port')
        else:
            self._audiostream_port = conf.get_int('audiostream', 'port') or 31337

        if conf.get('ddp', 'existing_stream_key'):
            self.stream_key = conf.get('ddp', 'existing_stream_key')
        else:
            self.stream_key = uuid.uuid4().get_hex()

        if conf.get('ddp', 'extra_params'):
            self.extra_params_list = conf.get('ddp', 'extra_params').split(';')
        else:
            self.extra_params_list = []
        logger.info('audiostream URI: {}'.format('http://' + self._stream_host + ':' + str(self._audiostream_port) + '/' + self.stream_key))

        dispatcher.connect('init', self.on_init)
        dispatcher.connect('recorder-vumeter', self.vumeter)
        dispatcher.connect('timer-short', self.update_vu)
        dispatcher.connect('timer-short', self.heartbeat)
        dispatcher.connect('recorder-started', self.on_start_recording)
        dispatcher.connect('recorder-stopped', self.on_stop_recording)
        dispatcher.connect('recorder-status', self.on_rec_status_update)

    def run(self):
        self.connect()

    def connect(self):
        if not self.has_disconnected:
            try:
                self.client.connect()
            except Exception:
                logger.warn('DDP connection failed')

    def update(self, collection, query, update):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.update(
                    collection,
                    query,
                    update,
                    callback=self.update_callback)
            except Exception:
                logger.warn(
                    "Error updating document "
                    "{collection: %s, query: %s, update: %s}" %
                    (collection, query, update))

    def insert(self, collection, document):
        if self.client.connected and self.subscribedTo('GalicasterControl'):
            try:
                self.client.insert(
                    collection,
                    document,
                    callback=self.insert_callback)
            except Exception:
                logger.warn(
                    "Error inserting document {collection: %s, document: %s}" %
                    (collection, document))

    def heartbeat(self, element):
        if self.client.connected:
            self.update_images()
        else:
            self.connect()

    def on_start_recording(self, sender, id):
        self.recording = True
        self.currentMediaPackage = self.media_package_metadata(id)
        self.currentProfile = conf.get_current_profile().name
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$set': {
                    'currentMediaPackage': self.currentMediaPackage,
                    'currentProfile': self.currentProfile,
                    'recording': self.recording
                }
            })

    def on_stop_recording(self, mpid, sender=None):
        self.recording = False
        self.currentMediaPackage = None
        self.currentProfile = None
        self.update(
            'rooms', {
                '_id': self.id
            }, {
                '$unset': {
                    'currentMediaPackage': '',
                    'currentProfile': ''
                }, '$set': {
                    'recording': self.recording
                }
            })
        self.update_images(1.5)

    def on_init(self, data):
        self.update_images(1.5)

    def update_images(self, delay=0.0):
        worker = Thread(target=self._update_images, args=(delay,))
        worker.start()

    def _update_images(self, delay):
        time.sleep(delay)
        files = {}

        if not self.screenshot_file:
            # take a screenshot with pyscreenshot
            im = ImageGrab.grab(bbox=(0, 0, self._screen_width, self._screen_height), backend='imagemagick')
        else:
            try:
                # used if screenshot already exists
                im = Image.open(self.screenshot_file)
            except IOError as e:
                logger.warn("Unable to open screenshot file {0}".format(self.screenshot_file))
                return
        output = cStringIO.StringIO()
        image_format = 'JPEG'
        if not self.high_quality:
            im.thumbnail((640, 360), Image.ANTIALIAS)
        else:
            image_format = 'PNG'

        if im.mode != "RGB":
            im = im.convert("RGB")
        im.save(output, format=image_format) # to reduce jpeg size use param: optimize=True
        files['galicaster'] = ('galicaster.jpg', output.getvalue(),
                               'image/jpeg')
        try:
            # add verify=False for testing self signed certs
            requests.post(
                "%s/image/%s" %
                (self._http_host, self.id), files=files, auth=(
                    self._user, self._password)) # to ignore ssl verification, use param: verify=False
        except Exception:
            logger.warn('Unable to post images')

    def vumeter(self, element, data, data_chan2, vu_bool):
        if data == "Inf":
            data = 0
        else:
            if data < -self.vu_range:
                data = -self.vu_range
            elif data > 0:
                data = 0
        self.vu_data = int(((data + self.vu_range) / float(self.vu_range)) * 100)

    def update_vu(self, element):
        if self.vu_data != self.last_vu:
                update = {'vumeter': self.vu_data}
                self.update('rooms', {'_id': self.id}, {'$set': update})
                self.last_vu = self.vu_data

    def on_rec_status_update(self, element, data):
        if data == 'paused':
            is_paused = True
        else:
            is_paused = False
        if is_paused:
            self.update_images(.75)
        if self.paused == is_paused:
            self.update(
                'rooms', {
                    '_id': self.id}, {
                    '$set': {
                        'paused': is_paused}})
            self.paused = is_paused
        if data == 'recording':
            self.update_images(.75)

    def media_package_metadata(self, id):
        mp = context.get('recorder').current_mediapackage
        line = mp.metadata_episode
        duration = mp.getDuration()
        line["duration"] = long(duration / 1000) if duration else None
        # FIXME Does series_title need sanitising as well as duration?
        created = mp.getDate()
        # line["created"] = calendar.timegm(created.utctimetuple())
        for key, value in mp.metadata_series.iteritems():
            line["series_" + key] = value
        for key, value in line.iteritems():
            if value in [None, []]:
                line[key] = ''
        # return line
        return line

    def subscription_callback(self, error):
        if error:
            logger.warn("Subscription callback returned error: %s" % error)

    def insert_callback(self, error, data):
        if error:
            logger.warn("Insert callback returned error: %s" % error)

    def update_callback(self, error, data):
        if error:
            logger.warn("Update callback returned error: %s" % error)

    def on_subscribed(self, subscription):
        if(subscription == 'GalicasterControl'):
            me = self.client.find_one('rooms')
            # Data to push when inserting or updating
            data = {
                'displayName': self.displayName,
                'ip': self.ip,
                'paused': self.paused,
                'recording': self.recording,
                'heartbeat': int(time.time()),
                'camAvailable': self.cam_available,
                'inputs': self.inputs(),
                'stream': {
                    'host': self._stream_host,
                    'port': self._audiostream_port,
                    'key': self.stream_key
                }
            }
            # Parse extra Meteor Mongodb collection elements and append
            for params in self.extra_params_list:
                param = params.split(':')
                data[param[0]] = param[1]

            if self.currentMediaPackage:
                data['currentMediaPackage'] = self.currentMediaPackage
            if self.currentProfile:
                data['currentProfile'] = self.currentProfile

            if me:
                # Items to unset
                unset = {}
                if not self.currentMediaPackage:
                    unset['currentMediaPackage'] = ''
                if not self.currentProfile:
                    unset['currentProfile'] = ''

                # Update to push
                update = {
                    '$set': data
                }

                if unset:
                    update['$unset'] = unset
                self.update('rooms', {'_id': self.id}, update)
            else:
                data['_id'] = self.id
                self.insert('rooms', data)

    def inputs(self):
        inputs = {
            'presentations': ['Presentation']
        }
        inputs['cameras'] = []
        labels = conf.get('ddp', 'cam_labels')
        cam_labels = []
        if labels:
            cam_labels = [l.strip() for l in labels.split(',')]
        for i in range(0, self.cam_available):
            label = cam_labels[i] if i < len(
                cam_labels) else "Camera %d" % (i + 1)
            inputs['cameras'].append(label)
        return inputs

    def on_added(self, collection, id, fields):
        pass

    def on_changed(self, collection, id, fields, cleared):
        me = self.client.find_one('rooms')
        if self.paused != me['paused']:
            self.set_paused(me['paused'])

        if context.get('recorder').is_recording() != me['recording']:
            self.set_recording(me)

    def on_removed(self, collection, id):
        self.on_subscribed(None)

    def set_paused(self, new_status):
        if not self.paused:
            self.paused = new_status
            context.get('recorder').pause()
        else:
            self.paused = False
            context.get('recorder').resume()


    def set_recording(self, me):
        self.recording = me['recording']
        if self.recording:
            # FIXME: Metadata isn't passed to recorder
            meta = me.get('currentMediaPackage', {}) or {}
            profile = me.get('currentProfile', 'nocam')
            series = (meta.get('series_title', ''), meta.get('isPartOf', ''))
            user = {'user_name': meta.get('creator', ''),
                    'user_id': meta.get('rightsHolder', '')}
            title = meta.get('title', 'Unknown')
            context.get('recorder').record()
        else:
            context.get('recorder').stop()

    def on_connected(self):
        logger.info('Connected to Meteor')
        token = conf.get('ddp', 'token')
        self.client.login(self._user, self._password, token=token)

    def on_logged_in(self, data):
        conf.set('ddp', 'token', data['token'])
        conf.update()
        try:
            self.client.subscribe(
                'GalicasterControl',
                params=[
                    self.id],
                callback=self.subscription_callback)
        except Exception:
            logger.warn('DDP subscription failed')

    def on_closed(self, code, reason):
        self.has_disconnected = True
        logger.error('Disconnected from Meteor: err %d - %s' % (code, reason))

    def subscribedTo(self, publication):
        return self.client.subscriptions.get(publication) != None
예제 #15
0
def subscription_callback(error):
    if error:
        print(error)


def login_callback(error):
    if error:
        print(error)


client.on('connected', connected)
client.on('logged_in', logged_in)
client.on('subscribed', subscribed)
client.on('unsubscribed', unsubscribed)
client.on('added', added)
client.on('changed', changed)

client.connect()
client.login('pythonSum', 'Secret1234'.encode())
client.subscribe('pythonPublication')

# (sort of) hacky way to keep the client alive
# ctrl + c to kill the script
while True:
    try:
        time.sleep(1)
    except KeyboardInterrupt:
        break

client.unsubscribe('publicLists')