Module socialization.ccs

Expand source code
from .base_god_service import BaseGodService
from .base_goddess_service import BaseGoddessService
from .json_ws_active_service import JSONWebsocketActiveService
from .json_ws_passive_service import JSONWebsocketPassiveService
from .wrapped_god_service import WrappedGodService
from .wrapped_goddess_service import WrappedGoddessService
from .wrapped_goddess_db_agent import WrappedGoddessDBAgent
from ..import codes

__all__ = ["BaseGodService", "BaseGoddessService", "JSONWebsocketActiveService", "JSONWebsocketPassiveService", "WrappedGodService", "WrappedGoddessService", "WrappedGoddessDBAgent", "codes"]

Sub-modules

socialization.ccs.base_god_service
socialization.ccs.base_goddess_service
socialization.ccs.json_ws_active_service
socialization.ccs.json_ws_passive_service
socialization.ccs.wrapped_god_service
socialization.ccs.wrapped_goddess_db_agent
socialization.ccs.wrapped_goddess_service

Classes

class BaseGodService
Expand source code
class BaseGodService(JSONWebsocketActiveService):
    def __init__(self):
        super().__init__()
        self.codes = codes
        self.path = '' # possible new route path for websocket
        
    def on_open(self, ws):
        print('BaseGodService opened')
        
    # override this function to handle data
    def _handle_data_dict(self, data, ws):
        # print(f'BaseGodService: _handle_data_dict received {data} at websocket {ws}')
        if data['code'] == self.codes.MESSAGE_TO_CCS:
            self._handle_message_to_ccs(data, ws, self.path)
        elif data['code'] == self.codes.COMMAND_TO_CCS:
            self._handle_command_to_ccs(data, ws, self.path)
        else:
            pass
    
    def _handle_message_to_ccs(self, data, ws, path):
        data['extra']['origin'] = self.__class__.__name__
        data['extra']['type_code'] += 20000

        # 3xxxx to 5xxxx
        self._send_data_to_ws(ws, self.codes.MESSAGE_FROM_CCS, **data['extra'])               

    def _handle_command_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        if 20000 <= type_code <= 29999:
            self._handle_basic_command(data, ws, path)
        elif 80000 <= type_code <= 89999:
            self._handle_notice(data, ws, path)

        elif 90000 <= type_code <= 99999:
            self._handle_feature(data, ws, path)

        else:
            pass
    
    
    def _handle_basic_command(self, data, ws, path):
        pass

    def _handle_notice(self, data, ws, path):
        pass

    def _handle_feature(self, data, ws, path):
        pass
   
    def _send_ccs_operation(self, data, ws, path):
        code = data['code']
        if code == self.codes.COPERATION_GOD_RECONNECT:
            user_id = data['extra']['user_id']
            password = data['extra']['password']
            self._send_data_to_ws(ws, code, user_id=user_id, password=password)
        else:
            pass
        # # print('BaseGoddessService ccs_operation: ', data)
        # code = data['code']
        # password = data['extra']['password']
        # ccs_id = data['extra']['ccs_id']
        # if code == self.codes.COPERATION_LOGIN:
        #     self._send_data_to_ws(ws, self.codes.COPERATION_LOGIN, ccs_id=ccs_id, password=password)
        # else:
        #     pass

    def _send_command_down(self, ws, type_code, **kwargs):
        self._send_data_to_ws(ws, self.codes.COMMAND_FROM_CCS, type_code=type_code, **kwargs)

Ancestors

Subclasses

Methods

def on_open(self, ws)
Expand source code
def on_open(self, ws):
    print('BaseGodService opened')
class BaseGoddessService (port=9000)
Expand source code
class BaseGoddessService(JSONWebsocketPassiveService):
    def __init__(self, port=9000):
        super().__init__(port=port)
        self.codes = codes

    async def _handle_data_dict(self, data, ws, path):
        # print('BaseGoddessService _handle_data_dict: ', data)
        if data['code'] == self.codes.MESSAGE_TO_CCS:
            await self._handle_message_to_ccs(data, ws, path)
        elif data['code'] == self.codes.COMMAND_TO_CCS:
            await self._handle_command_to_ccs(data, ws, path)
        # elif data['code'] == self.codes.QUERY_RESPOND_TO_CCS:
        #     await self._handle_query_respond_to_ccs(data, ws, path)
        else:
            pass

    async def _handle_message_to_ccs(self, data, ws, path):
        data['extra']['origin'] = self.__class__.__name__
        data['extra']['type_code'] += 20000

        # 3xxxx to 5xxxx
        await self._send_data_to_ws(ws, self.codes.MESSAGE_FROM_CCS, **data['extra'])               

    async def _handle_command_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        # print('BaseGoddessService _handle_command_to_ccs: ', data)
        if 20000 <= type_code <= 29999:
            await self._handle_basic_command(data, ws, path)
        elif 80000 <= type_code <= 89999:
            await self._handle_notice(data, ws, path)
        elif 90000 <= type_code <= 99999:
            await self._handle_feature(data, ws, path)

        else:
            pass
    
    async def _handle_basic_command(self, data, ws, path):
        pass

    async def _handle_notice(self, data, ws, path):
        pass

    async def _handle_feature(self, data, ws, path):
        pass

    async def _send_command_down(self, ws, type_code, **kwargs):
        await self._send_data_to_ws(ws, self.codes.COMMAND_FROM_CCS, type_code=type_code, **kwargs)

    async def _send_ccs_operation(self, data, ws, path):
        pass

Ancestors

Subclasses

class JSONWebsocketActiveService
Expand source code
class JSONWebsocketActiveService:
    def __init__(self) -> None:
        pass
    
    # new websocket, do not try to connect
    def create_websocket(self, uri, on_open=None, on_message=None, on_error=None, on_close=None):
        print('create_websocket')
        ws = websocket.WebSocketApp(uri,
            on_open=on_open or self.on_open,
            on_message=on_message or self.on_message,
            on_error=on_error or self.on_error,
            on_close=on_close or self.on_close
        )
        return ws
    
    # new connections could be created in response to on_XXX following same rules
    def create_connection(self, uri, on_open=None, on_message=None, on_error=None, on_close=None):
        print('create_connection')
        # ws = websocket.WebSocketApp(uri,
        #     on_open=on_open or self.on_open,
        #     on_message=on_message or self.on_message,
        #     on_error=on_error or self.on_error,
        #     on_close=on_close or self.on_close
        # )
        ws = self.create_websocket(uri, on_open, on_message, on_error, on_close)
        # ws.run_forever(reconnect=5)
        ws.run_forever(skip_utf8_validation=True, dispatcher=rel, reconnect=5)
        # print('before yield ws run_forever')
        # yield ws
        # print('start run_forever')
        # keep_on = True # use the return of run_forever to capture KeyboardInterrupt
        # while keep_on:
        #     try:
        #         keep_on = ws.run_forever(skip_utf8_validation=True, ping_interval=10, ping_timeout=8)
        #     except Exception as e:
        #         gc.collect()
        #         print("Websocket connection Error : {e}")                    
        #     print("Reconnecting websocket after 5 sec")
        #     time.sleep(5)
        return ws

    def on_message(self, ws, message):
        import datetime
        ct = datetime.datetime.now()
        print("on_message on_message on_message", ct)
        self._safe_handle(ws, message)

    def on_error(self, ws, error):
        print(f'Male.on_error: {error}')

    def on_close(self, ws, close_status_code, close_msg):
        print(f"Male.on_close: {ws} closed with status code {close_status_code} and message {close_msg}")
        # print ("Retry connection: %s" % time.ctime())
        # time.sleep(5)
        # self.run() # retry per 5 seconds
    
    # override this function to do something when connection is established
    def on_open(self, ws):
        pass

    @staticmethod
    def _make_data_dict(code, **extra_args):
        return {
            'code': code,
            'extra': extra_args
        }

    def _send_data_to_ws(self, ws, code, **extra_args):
        data_dict = self._make_data_dict(code=code, **extra_args)
        self._safe_send(ws, data_dict)

    def _safe_send(self, ws, data):
        try:
            if isinstance(data, dict):
                data = json.dumps(data)
            else:
                pass
            import datetime;
            ct = datetime.datetime.now()
            print("active send", ct)
            ws.send(data)
            return True

        except websocket.WebSocketConnectionClosedException as e:
            print('Male: _safe_send: Connection Closed Exception.', e)
            
            return False
        except Exception as e:
            traceback.print_exc()
            print('Male: self._safe_send: Exception Occurs', e)
            
            return False

    def echo(self, ws, message):
        print(f'echo() received message: {message}')
        message = "I got your message: {}".format(message)
        self._safe_send(ws, message)

    def _safe_handle(self, ws, message):
        
        import datetime;

        # # ct stores current time
        
        try:
            ct = datetime.datetime.now()
            print("before social active _safe_handle current time:-", ct)
            data = json.loads(message)
            # ct = datetime.datetime.now()
            # print('data', data, ct)
            self._handle_data_dict(data, ws)

        except ValueError as e:
            # traceback.print_exc()
            print(f'Male: _safe_handle received non-json message {message}')
        
        except Exception as e:
            print('Male: Server function error!')
            traceback.print_exc()
        
        # import datetime;

        # # ct stores current time
        # ct = datetime.datetime.now()
        # print("after social active _safe_handle current time:-", ct)

    # override this function to handle data
    def _handle_data_dict(self, data, ws):
        print(f'Male default: _handle_data_dict received {data} at websocket {ws}')
        

    def run(self):
        uri = 'wss://frog.4fun.chat/social'
        self.create_connection(uri)
        # print("after create_connection")
        rel.signal(2, rel.abort)  # Keyboard Interrupt, one for all connections
        # print("after signal")
        rel.dispatch()
        # print("after everything in run")
    
    # def run(self):
    #     uri = 'wss://frog.4fun.chat/social'
    #     ws = self.create_connection(uri)
    #     # _thread.start_new_thread(self.create_connection, (uri,))
    #     # # ws = websocket.WebSocketApp("ws://127.0.0.1:3000", on_open = on_open, on_close = on_close)
    #     # wst = threading.Thread(target=ws.run_forever())
    #     # wst.daemon = True
    #     # print('before start')
    #     # wst.start()
    #     # print('after start')

Subclasses

Methods

def create_connection(self, uri, on_open=None, on_message=None, on_error=None, on_close=None)
Expand source code
def create_connection(self, uri, on_open=None, on_message=None, on_error=None, on_close=None):
    print('create_connection')
    # ws = websocket.WebSocketApp(uri,
    #     on_open=on_open or self.on_open,
    #     on_message=on_message or self.on_message,
    #     on_error=on_error or self.on_error,
    #     on_close=on_close or self.on_close
    # )
    ws = self.create_websocket(uri, on_open, on_message, on_error, on_close)
    # ws.run_forever(reconnect=5)
    ws.run_forever(skip_utf8_validation=True, dispatcher=rel, reconnect=5)
    # print('before yield ws run_forever')
    # yield ws
    # print('start run_forever')
    # keep_on = True # use the return of run_forever to capture KeyboardInterrupt
    # while keep_on:
    #     try:
    #         keep_on = ws.run_forever(skip_utf8_validation=True, ping_interval=10, ping_timeout=8)
    #     except Exception as e:
    #         gc.collect()
    #         print("Websocket connection Error : {e}")                    
    #     print("Reconnecting websocket after 5 sec")
    #     time.sleep(5)
    return ws
def create_websocket(self, uri, on_open=None, on_message=None, on_error=None, on_close=None)
Expand source code
def create_websocket(self, uri, on_open=None, on_message=None, on_error=None, on_close=None):
    print('create_websocket')
    ws = websocket.WebSocketApp(uri,
        on_open=on_open or self.on_open,
        on_message=on_message or self.on_message,
        on_error=on_error or self.on_error,
        on_close=on_close or self.on_close
    )
    return ws
def echo(self, ws, message)
Expand source code
def echo(self, ws, message):
    print(f'echo() received message: {message}')
    message = "I got your message: {}".format(message)
    self._safe_send(ws, message)
def on_close(self, ws, close_status_code, close_msg)
Expand source code
def on_close(self, ws, close_status_code, close_msg):
    print(f"Male.on_close: {ws} closed with status code {close_status_code} and message {close_msg}")
    # print ("Retry connection: %s" % time.ctime())
    # time.sleep(5)
    # self.run() # retry per 5 seconds
def on_error(self, ws, error)
Expand source code
def on_error(self, ws, error):
    print(f'Male.on_error: {error}')
def on_message(self, ws, message)
Expand source code
def on_message(self, ws, message):
    import datetime
    ct = datetime.datetime.now()
    print("on_message on_message on_message", ct)
    self._safe_handle(ws, message)
def on_open(self, ws)
Expand source code
def on_open(self, ws):
    pass
def run(self)
Expand source code
def run(self):
    uri = 'wss://frog.4fun.chat/social'
    self.create_connection(uri)
    # print("after create_connection")
    rel.signal(2, rel.abort)  # Keyboard Interrupt, one for all connections
    # print("after signal")
    rel.dispatch()
    # print("after everything in run")
class JSONWebsocketPassiveService (port=7654)
Expand source code
class JSONWebsocketPassiveService:
    def __init__(self, port=7654):
        self.port = port

    @property
    def _timestamp(self):
        now = datetime.now(timezone.utc)
        epoch = datetime(1970, 1, 1, tzinfo=timezone.utc) # use POSIX epoch
        return (now - epoch) // timedelta(microseconds=1) # or `/ 1e3` for float

    @staticmethod
    def _make_data_dict(code, **extra_args):
        return {
            'code': code,
            'extra': extra_args
        }

    async def _safe_send(self, ws, data):
        # import datetime;

        # ct stores current time
        # ct = datetime.datetime.now()
        # print("before social passive _safe_sendcurrent time:-", ct, data)
        
        try:
            if isinstance(data, dict):
                data = json.dumps(data)
            else:
                pass
            
            await ws.send(data)
            # ct stores current time
            # ct = datetime.datetime.now()
            # print("after social passive _safe_sendcurrent time:-", ct)
            return True
        except websockets.ConnectionClosedOK as e:
            print('_safe_send: Connection Closed OK Exception.', e)
            
            return False
        except websockets.ConnectionClosed as e:
            print('_safe_send: Connection Closed Exception.', e)
            
            return False
        except Exception as e:
            traceback.print_exc()
            print('self._safe_send: Exception Occurs', e)
            
            return False
        
        
    async def _send_data_to_ws(self, ws, code, **extra_args):
        data_dict = self._make_data_dict(code=code, **extra_args)
        await self._safe_send(ws, data_dict)

    async def echo(self, ws, message):
        print(f'echo() received message: {message}')
        message = "I got your message: {}".format(message)
        await self._safe_send(ws, message)

    async def _safe_handle(self, ws, path):
        # import datetime;

        # ct stores current time
        # ct = datetime.datetime.now()
        # print("before social passive _safe_handle current time:-", ct)
        try:    # This websocket may be closed
            async for message in ws:
                try:
                    import datetime
                    ct = datetime.datetime.now()
                    print("passive handle", ct, message)
                    data = json.loads(message)
                    await self._handle_data_dict(data, ws, path)

                
                except Exception as e:
                    traceback.print_exc()
                    await self._handle_internal_error(e, ws, path)
            # ct = datetime.datetime.now()
            # print("after social passive _safe_handle current time:-", ct)
        except Exception as e:
            print('Exception in handle(): ', e)

    async def _handle_data_dict(self, data, ws, path):
        await self._safe_send(ws, 'Female: _handle_data_dict Not Implemented')

    async def _handle_internal_error(self, e, ws, path):
        await self._safe_send(ws, f'Female._handle_internal_error: {e}')

    # call this for the coroutine to start
    def get_server_coroutine(self):
        return websockets.serve(self._safe_handle, 'localhost', self.port)

Subclasses

Methods

async def echo(self, ws, message)
Expand source code
async def echo(self, ws, message):
    print(f'echo() received message: {message}')
    message = "I got your message: {}".format(message)
    await self._safe_send(ws, message)
def get_server_coroutine(self)
Expand source code
def get_server_coroutine(self):
    return websockets.serve(self._safe_handle, 'localhost', self.port)
class WrappedGodService (name='WrappedGodService')

This is the wrapped God service. CCS provides a various of features for users in corresponding channels to use.

Expand source code
class WrappedGodService(BaseGodService):
    """
        This is the wrapped God service.
        CCS provides a various of features for users in corresponding channels to use.
    """

    def __init__(self, name='WrappedGodService'):
        super().__init__()
        self.agent = WrappedGoddessDBAgent(name+".json")
        self.uri = None
        self.name = 'WrappedGodService'
        self.feature_commands = []

        self.basic_command_func_map = {}
        self.notice_func_map = {}
        self.feature_func_map = {}

        self.prompt = {}

        self.message_func_map = {}
        
        self.temp_msg_map = {}

    def on_open(self, ws):
        """
            Default behavior "on_open" for websocket.
            You can override this method to customize your own behavior.
            Also applies to "on_message", "on_error" and "on_close".
        """
        data = {'code': self.codes.COPERATION_GOD_RECONNECT, 'extra': {
            'user_id': self.user_id, 'password': self.password}}
        print('SocialGodService send query: run: ', data)
        self._send_ccs_operation(data, ws, None)
        print(self.name, 'opened')

    def add_feature(self, name, code, prompts, func=None):
        """
            Add a feature to the service.
            Every feature contains a name, a code and a prompt.
            The code is always a five-digited int, and the first number must be 9, like 90001.
            The prompt here will be replaced by "params" in the future.
            Now it's the string that shows to the user in the input bar.
            You do not need to specify a handler func to the feature, but it's recommended to do so.
            If one feature with no handler is triggered, an exception will be raised.
            You can use set_feature_handle to set a handler for a feature.
        """
        self.feature_commands.append(
            {'name': name, 'code': code, 'prompts': [prompts] if prompts else []})
        self.prompt[code] = prompts
        if func:
            self.feature_func_map[code] = func

    def remove_feature(self, code):
        """
            Remove a feature from the service.
            The feature is specified by its code.
        """
        self.feature_commands = list(
            filter(lambda x: x['code'] != code, self.feature_commands))
        self.feature_func_map.pop(code, None)

    def set_basic_command_handle(self, code, func):
        """
            Set a handler for a basic command.
            The command is specified by its code.
            Some commands will be like "help", "quit".
        """
        self.basic_command_func_map[code] = func

    def set_notice_handle(self, code, func):
        """
            Set a handler for a notice.
            The notice is specified by its code.
            Some notices will be like "user joined the channel", "user left the channel".
        """
        self.notice_func_map[code] = func

    def set_feature_handle(self, code, func):
        """
            Set a handler for a feature.
            The feature is specified by its code.
        """
        self.feature_func_map[code] = func

    def set_message_handle(self, code, func):
        """
            Set a handler for a message.
            This code is always MESSAGE_TO_CCS.
        """
        self.message_func_map[code] = func

    def _handle_data_dict(self, data, ws):
        print(
            f'WrappedGodService: _handle_data_dict received {data} at websocket {ws}')
        if data['code'] == self.codes.MESSAGE_TO_CCS:
            try:
                self._handle_message_to_ccs(data, ws, self.path)
            except Exception as e:
                print('WrappedGodService: _handle_message_to_ccs error: ', e)
        elif data['code'] == self.codes.COMMAND_TO_CCS:
            try:
                self._handle_command_to_ccs(data, ws, self.path)
            except Exception as e:
                print('WrappedGodService: _handle_command_to_ccs error: ', e)
        else:
            raise Exception('no handler for code', data['code'])

    def _handle_message_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.message_func_map:
            self.message_func_map[type_code](self, data, ws, path)
        else:
            raise Exception('no message function for code', type_code)

    def _handle_command_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        if 20000 <= type_code <= 29999:
            self._handle_basic_command(data, ws, path)
        elif 80000 <= type_code <= 89999:
            self._handle_notice(data, ws, path)
        elif 90000 <= type_code <= 99999:
            self._handle_feature(data, ws, path)
        else:
            raise Exception('no command function for code', type_code)

    def _handle_basic_command(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.basic_command_func_map:
            self.basic_command_func_map[type_code](self, data, ws, path)
        else:
            raise Exception('no basic command function for code', type_code)

    def _handle_notice(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.notice_func_map:
            self.notice_func_map[type_code](self, data, ws, path)
        else:
            raise Exception('no notice function for code', type_code)

    def _handle_feature(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.feature_func_map:
            self.feature_func_map[type_code](self, data, ws, path)
        else:
            raise Exception('no feature function for code', type_code)

    def _send_ccs_operation(self, data, ws, path):
        print('WrappedGodService ccs_operation: ', data)
        code = data['code']
        password = data['extra']['password']
        user_id = data['extra']['user_id']
        if code == self.codes.COPERATION_GOD_RECONNECT:
            self._send_data_to_ws(
                ws, self.codes.COPERATION_GOD_RECONNECT, user_id=user_id, password=password)
        else:
            raise Exception('no ccs operation for code', code)

    def _send_command_down(self, ws, type_code, **kwargs):
        self._send_data_to_ws(ws, self.codes.COMMAND_FROM_CCS,
                              type_code=type_code, **kwargs)

    def _send_message_down(self, ws, type_code, channel_id, to_user_ids, origin, **kwargs):
        self._send_data_to_ws(ws, self.codes.MESSAGE_FROM_CCS,
                              type_code=type_code, channel_id=channel_id, to_user_ids=to_user_ids,
                              origin=origin, **kwargs)

    def broadcast_command_text(self, data, ws, text, clear=False):
        """
            Broadcast a text to all users in a channel.
        """
        channel_id = data['extra']['channel_id']
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id=channel_id,
            to_user_ids=self.agent.get_channel_user_list(channel_id),
            args={'text': text, 'clear': clear}
        )

    def broadcast_command_image(self, data, ws, url):
        """
            Broadcast an image to all users in a channel.
        """
        channel_id = data['extra']['channel_id']
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
            channel_id=channel_id,
            to_user_ids=self.agent.get_channel_user_list(channel_id),
            args={
                'type': 'url',
                'image': url
            }
        )

    def reply_command_text(self, data, ws, text, clear=False):
        """
            Reply a text to a single user.
        """
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id=data['extra']['channel_id'],
            to_user_ids=[data['extra']['user_id']],
            args={'text': text, 'clear': clear}
        )

    def reply_command_image(self, data, ws, url):
        """
            Reply an image to a single user.
        """
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
            channel_id=data['extra']['channel_id'],
            to_user_ids=[data['extra']['user_id']],
            args={'type': 'url',  'image': url}
        )

    def whistle_sender_command_text(
        self, data, ws, *,
        text_to_sender, text_to_others,
        clear_sender=False, clear_others=False
    ):
        """
            Dog whistle.
        """
        channel_id = data['extra']['channel_id']
        # to sender
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id=channel_id,
            to_user_ids=[data['extra']['user_id']],
            args={'text': text_to_sender, 'clear': clear_sender}
        )
        # to others
        self._send_command_down(
            ws,
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id=channel_id,
            to_user_ids=list(set(self.agent.get_channel_user_list(
                channel_id)) - {data['extra']['user_id']}),
            args={'text': text_to_others, 'clear': clear_others}
        )

    def set_account(self, user_id, password):
        """
            Set the account for the service.
            Fetch these in CH0000.
        """
        self.user_id = user_id
        self.password = password

    def run(self):
        """
            Run the service.
        """
        uri = 'wss://frog.4fun.chat/social' if self.uri is None else self.uri
        ws = self.create_connection(
            uri, self.on_open, self.on_message, self.on_error, self.on_close)

        rel.signal(2, rel.abort)  # Keyboard Interrupt, one for all connections
        rel.dispatch()

Ancestors

Methods

def add_feature(self, name, code, prompts, func=None)

Add a feature to the service. Every feature contains a name, a code and a prompt. The code is always a five-digited int, and the first number must be 9, like 90001. The prompt here will be replaced by "params" in the future. Now it's the string that shows to the user in the input bar. You do not need to specify a handler func to the feature, but it's recommended to do so. If one feature with no handler is triggered, an exception will be raised. You can use set_feature_handle to set a handler for a feature.

Expand source code
def add_feature(self, name, code, prompts, func=None):
    """
        Add a feature to the service.
        Every feature contains a name, a code and a prompt.
        The code is always a five-digited int, and the first number must be 9, like 90001.
        The prompt here will be replaced by "params" in the future.
        Now it's the string that shows to the user in the input bar.
        You do not need to specify a handler func to the feature, but it's recommended to do so.
        If one feature with no handler is triggered, an exception will be raised.
        You can use set_feature_handle to set a handler for a feature.
    """
    self.feature_commands.append(
        {'name': name, 'code': code, 'prompts': [prompts] if prompts else []})
    self.prompt[code] = prompts
    if func:
        self.feature_func_map[code] = func
def broadcast_command_image(self, data, ws, url)

Broadcast an image to all users in a channel.

Expand source code
def broadcast_command_image(self, data, ws, url):
    """
        Broadcast an image to all users in a channel.
    """
    channel_id = data['extra']['channel_id']
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
        channel_id=channel_id,
        to_user_ids=self.agent.get_channel_user_list(channel_id),
        args={
            'type': 'url',
            'image': url
        }
    )
def broadcast_command_text(self, data, ws, text, clear=False)

Broadcast a text to all users in a channel.

Expand source code
def broadcast_command_text(self, data, ws, text, clear=False):
    """
        Broadcast a text to all users in a channel.
    """
    channel_id = data['extra']['channel_id']
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id=channel_id,
        to_user_ids=self.agent.get_channel_user_list(channel_id),
        args={'text': text, 'clear': clear}
    )
def on_open(self, ws)

Default behavior "on_open" for websocket. You can override this method to customize your own behavior. Also applies to "on_message", "on_error" and "on_close".

Expand source code
def on_open(self, ws):
    """
        Default behavior "on_open" for websocket.
        You can override this method to customize your own behavior.
        Also applies to "on_message", "on_error" and "on_close".
    """
    data = {'code': self.codes.COPERATION_GOD_RECONNECT, 'extra': {
        'user_id': self.user_id, 'password': self.password}}
    print('SocialGodService send query: run: ', data)
    self._send_ccs_operation(data, ws, None)
    print(self.name, 'opened')
def remove_feature(self, code)

Remove a feature from the service. The feature is specified by its code.

Expand source code
def remove_feature(self, code):
    """
        Remove a feature from the service.
        The feature is specified by its code.
    """
    self.feature_commands = list(
        filter(lambda x: x['code'] != code, self.feature_commands))
    self.feature_func_map.pop(code, None)
def reply_command_image(self, data, ws, url)

Reply an image to a single user.

Expand source code
def reply_command_image(self, data, ws, url):
    """
        Reply an image to a single user.
    """
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
        channel_id=data['extra']['channel_id'],
        to_user_ids=[data['extra']['user_id']],
        args={'type': 'url',  'image': url}
    )
def reply_command_text(self, data, ws, text, clear=False)

Reply a text to a single user.

Expand source code
def reply_command_text(self, data, ws, text, clear=False):
    """
        Reply a text to a single user.
    """
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id=data['extra']['channel_id'],
        to_user_ids=[data['extra']['user_id']],
        args={'text': text, 'clear': clear}
    )
def run(self)

Run the service.

Expand source code
def run(self):
    """
        Run the service.
    """
    uri = 'wss://frog.4fun.chat/social' if self.uri is None else self.uri
    ws = self.create_connection(
        uri, self.on_open, self.on_message, self.on_error, self.on_close)

    rel.signal(2, rel.abort)  # Keyboard Interrupt, one for all connections
    rel.dispatch()
def set_account(self, user_id, password)

Set the account for the service. Fetch these in CH0000.

Expand source code
def set_account(self, user_id, password):
    """
        Set the account for the service.
        Fetch these in CH0000.
    """
    self.user_id = user_id
    self.password = password
def set_basic_command_handle(self, code, func)

Set a handler for a basic command. The command is specified by its code. Some commands will be like "help", "quit".

Expand source code
def set_basic_command_handle(self, code, func):
    """
        Set a handler for a basic command.
        The command is specified by its code.
        Some commands will be like "help", "quit".
    """
    self.basic_command_func_map[code] = func
def set_feature_handle(self, code, func)

Set a handler for a feature. The feature is specified by its code.

Expand source code
def set_feature_handle(self, code, func):
    """
        Set a handler for a feature.
        The feature is specified by its code.
    """
    self.feature_func_map[code] = func
def set_message_handle(self, code, func)

Set a handler for a message. This code is always MESSAGE_TO_CCS.

Expand source code
def set_message_handle(self, code, func):
    """
        Set a handler for a message.
        This code is always MESSAGE_TO_CCS.
    """
    self.message_func_map[code] = func
def set_notice_handle(self, code, func)

Set a handler for a notice. The notice is specified by its code. Some notices will be like "user joined the channel", "user left the channel".

Expand source code
def set_notice_handle(self, code, func):
    """
        Set a handler for a notice.
        The notice is specified by its code.
        Some notices will be like "user joined the channel", "user left the channel".
    """
    self.notice_func_map[code] = func
def whistle_sender_command_text(self, data, ws, *, text_to_sender, text_to_others, clear_sender=False, clear_others=False)

Dog whistle.

Expand source code
def whistle_sender_command_text(
    self, data, ws, *,
    text_to_sender, text_to_others,
    clear_sender=False, clear_others=False
):
    """
        Dog whistle.
    """
    channel_id = data['extra']['channel_id']
    # to sender
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id=channel_id,
        to_user_ids=[data['extra']['user_id']],
        args={'text': text_to_sender, 'clear': clear_sender}
    )
    # to others
    self._send_command_down(
        ws,
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id=channel_id,
        to_user_ids=list(set(self.agent.get_channel_user_list(
            channel_id)) - {data['extra']['user_id']}),
        args={'text': text_to_others, 'clear': clear_others}
    )
class WrappedGoddessDBAgent (path)
Expand source code
class WrappedGoddessDBAgent:
    def __init__(self, path):
        self.path = path

        # create if not exist
        if not os.path.exists(self.path):
            with open(self.path, 'w') as f:
                json.dump({}, f)

    def leave_channel(self, channel_id, user_id):
        with open(self.path, 'r') as f:
            data = json.load(f)
        data["user_list"][channel_id].remove(user_id)
        # write
        with open(self.path, 'w') as f:
            json.dump(data, f)

    def join_channel(self, channel_id, user_id):
        with open(self.path, 'r') as f:
            data = json.load(f)
        if not channel_id in data:
            data["user_list"][channel_id] = []
        data["user_list"][channel_id].append(user_id)
        # write
        with open(self.path, 'w') as f:
            json.dump(data, f)

    def get_channel_user_list(self, channel_id):
        with open(self.path, 'r') as f:
            data = json.load(f)
        if not ("user_list" in data and channel_id in data["user_list"]):
            return []
        return data["user_list"][channel_id]

    def init_user_id_list(self, channel_id, user_ids):
        with open(self.path, 'r') as f:
            data = json.load(f)
        data["user_list"][channel_id] = user_ids or []
        with open(self.path, 'w') as f:
            json.dump(data, f)

    def init_dog_whistle(self, channel_id):
        with open(self.path, 'r') as f:
            data = json.load(f)
        data["dog_whistle"][channel_id] = []
        with open(self.path, 'w') as f:
            json.dump(data, f)

    def add_whistle_msg(self, channel_id, msg, recipients):
        with open(self.path, 'r') as f:
            data = json.load(f)
        data["dog_whistle"][channel_id].append({msg, recipients})
        with open(self.path, 'w') as f:
            json.dump(data, f)

    def get_whistle_recipients(self, channel_id, msg):
        with open(self.path, 'r') as f:
            data = json.load(f)
        if not ("dog_whistle" in data and channel_id in data["dog_whistle"] and msg in data["dog_whistle"][channel_id]):
            return []
        return data["dog_whistle"][channel_id][msg]

Methods

def add_whistle_msg(self, channel_id, msg, recipients)
Expand source code
def add_whistle_msg(self, channel_id, msg, recipients):
    with open(self.path, 'r') as f:
        data = json.load(f)
    data["dog_whistle"][channel_id].append({msg, recipients})
    with open(self.path, 'w') as f:
        json.dump(data, f)
def get_channel_user_list(self, channel_id)
Expand source code
def get_channel_user_list(self, channel_id):
    with open(self.path, 'r') as f:
        data = json.load(f)
    if not ("user_list" in data and channel_id in data["user_list"]):
        return []
    return data["user_list"][channel_id]
def get_whistle_recipients(self, channel_id, msg)
Expand source code
def get_whistle_recipients(self, channel_id, msg):
    with open(self.path, 'r') as f:
        data = json.load(f)
    if not ("dog_whistle" in data and channel_id in data["dog_whistle"] and msg in data["dog_whistle"][channel_id]):
        return []
    return data["dog_whistle"][channel_id][msg]
def init_dog_whistle(self, channel_id)
Expand source code
def init_dog_whistle(self, channel_id):
    with open(self.path, 'r') as f:
        data = json.load(f)
    data["dog_whistle"][channel_id] = []
    with open(self.path, 'w') as f:
        json.dump(data, f)
def init_user_id_list(self, channel_id, user_ids)
Expand source code
def init_user_id_list(self, channel_id, user_ids):
    with open(self.path, 'r') as f:
        data = json.load(f)
    data["user_list"][channel_id] = user_ids or []
    with open(self.path, 'w') as f:
        json.dump(data, f)
def join_channel(self, channel_id, user_id)
Expand source code
def join_channel(self, channel_id, user_id):
    with open(self.path, 'r') as f:
        data = json.load(f)
    if not channel_id in data:
        data["user_list"][channel_id] = []
    data["user_list"][channel_id].append(user_id)
    # write
    with open(self.path, 'w') as f:
        json.dump(data, f)
def leave_channel(self, channel_id, user_id)
Expand source code
def leave_channel(self, channel_id, user_id):
    with open(self.path, 'r') as f:
        data = json.load(f)
    data["user_list"][channel_id].remove(user_id)
    # write
    with open(self.path, 'w') as f:
        json.dump(data, f)
class WrappedGoddessService (port, uri, token, name='WrappedGoddessService')
Expand source code
class WrappedGoddessService(BaseGoddessService):
    def __init__(self, port, uri, token, name='WrappedGoddessService'):
        super().__init__(port)
        self.agent = WrappedGoddessDBAgent('/home/ubuntu/social_backend/yellow502goddess.json')
        self.uri = uri
        self.token = token
        self.name = name
        
        self.feature_commands = []

        self.basic_command_func_map = {
            self.codes.COMMAND_UP_FETCH_CHANNEL_USER_LIST: handle_command_fetch_user_list,
            self.codes.COMMAND_UP_FETCH_RECIPIENT_LIST: handle_command_fetch_recipient_list,
            self.codes.COMMAND_UP_FETCH_CCS_COMMAND_LIST: handle_command_fetch_cmd_list,
        }
        
        self.notice_func_map = {
            self.codes.NOTICE_ASK_AUTH_TOKEN: handle_notice_auth_token,
            self.codes.NOTICE_TAKE_OVER: handle_notice_take_over,
            self.codes.NOTICE_RELEASE: handle_notice_release,
            self.codes.NOTICE_USER_JOINED: handle_notice_user_joined,
        }
        self.feature_func_map = {}

        self.prompt = {}

        self.message_func_map = {}


    def add_feature(self, name, code, prompts, func=None):
        self.feature_commands.append(
            {'name': name, 'code': code, 'prompts': [prompts]})
        self.prompt[code] = prompts
        if func:
            self.feature_func_map[code] = func

    def set_basic_command_handle(self, code, func):
        self.basic_command_func_map[code] = func

    def set_notice_handle(self, code, func):
        self.notice_func_map[code] = func

    def set_feature_handle(self, code, func):
        self.feature_func_map[code] = func

    def set_message_handle(self, code, func):
        self.message_func_map[code] = func

    async def _handle_data_dict_core(self, data, ws, path):
        print(f'WrappedGoddessService: _handle_data_dict received {data}.')
        if data['code'] == self.codes.MESSAGE_TO_CCS:
            await self._handle_message_to_ccs(data, ws, path)
        elif data['code'] == self.codes.COMMAND_TO_CCS:
            await self._handle_command_to_ccs(data, ws, path)
        else:
            pass
        
    async def _handle_data_dict(self, data, ws, path):
        asyncio.ensure_future(self._handle_data_dict_core(data, ws, path))

    async def _handle_message_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.message_func_map:
            await self.message_func_map[type_code](self, data, ws, path)
        else:
            print('no message function for code', type_code)

    async def _handle_command_to_ccs(self, data, ws, path):
        type_code = data['extra']['type_code']
        if 20000 <= type_code <= 29999:
            await self._handle_basic_command(data, ws, path)
        elif 80000 <= type_code <= 89999:
            await self._handle_notice(data, ws, path)
        elif 90000 <= type_code <= 99999:
            await self._handle_feature(data, ws, path)
        else:
            pass

    async def _handle_basic_command(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.basic_command_func_map:
            await self.basic_command_func_map[type_code](self, data, ws, path)
        else:
            print('no basic_command function for code', type_code)
    


    async def _handle_notice(self, data, ws, path):
        type_code = data['extra']['type_code']
        
        print('receive notice:' + str(data))

        if type_code in self.notice_func_map:
            await self.notice_func_map[type_code](self, data, ws, path)
            
        else:
            if type_code == self.codes.NOTICE_USER_LEFT:
                channel_id = data['extra']['channel_id']
                # Here the CCS database may want to update to delete the new user
                # In the case of Social default Goddess, the database has been updated on OPERATION_JOIN
                user_id = data['extra']['user_id']
                self.agent.leave_channel(channel_id, user_id)
                user_ids = self.agent.get_channel_user_list(channel_id)
                print('user_ids', user_ids)
                # serial_numbers= self.agent.user_id_list_to_serial_number_list(user_ids)
                # alias = [self.serial_number_to_alias(serial_number) for serial_number in serial_numbers]
                # print('alias', alias)
                # await self._send_command_down(ws, self.codes.COMMAND_DOWN_UPDATE_CHANNEL_USER_LIST,
                #     channel_id=channel_id, to_user_ids=user_ids)
                # await self._send_command_down(ws, self.codes.COMMAND_DOWN_UPDATE_CCS_COMMAND_LIST,
                #     channel_id=channel_id, to_user_ids=user_ids)
                
                await self._send_command_down(ws, self.codes.COMMAND_DOWN_UPDATE_CHANNEL_USER_LIST,
                    channel_id=channel_id, to_user_ids=user_ids, user_ids=user_ids)
                # await self._send_command_down(ws, self.codes.COMMAND_DOWN_UPDATE_CCS_COMMAND_LIST,
                #     channel_id=channel_id, to_user_ids=user_ids, commands=self.feature_commands)
                
            elif type_code == self.codes.NOTICE_GET_CHANNEL_USER_LIST:
                print('ccs yellowbear notice get channel user list', data)    
                

            else:
                raise Exception('yellowbear goddess Unknown type_code: {}'.format(type_code))
        
        '''
        else:
            print('no notice function for code', type_code)
        '''

    async def _handle_feature(self, data, ws, path):
        type_code = data['extra']['type_code']
        if type_code in self.feature_func_map:
            await self.feature_func_map[type_code](self, data, ws, path)
        else:
            print('no feature function for code', type_code)

    async def _send_ccs_operation(self, data, ws, path):
        print('WrappedGoddessService ccs_operation: ', data)
        code = data['code']
        password = data['extra']['password']
        ccs_id = data['extra']['ccs_id']
        if code == self.codes.OPERATION_CCS_LOGIN:
            await self._send_data_to_ws(
                ws, self.codes.OPERATION_CCS_LOGIN, ccs_id=ccs_id, password=password)
        else:
            pass

    async def _send_command_down(self, ws, type_code, **kwargs):
        await self._send_data_to_ws(ws, self.codes.COMMAND_FROM_CCS,
                              type_code=type_code, **kwargs)
    
    ### Some wrapped functions, use them to make your work easier!
    
    # broadcast to all users
    async def broadcast_command_text(self, channel_id, ws, text, clear=False):
        await self._send_command_down(
            ws, 
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id=channel_id, 
            to_user_ids=self.agent.get_channel_user_list(channel_id), 
            args={'text': text, 'clear': clear}
        )
    
    async def broadcast_command_image(self, channel_id, ws, url, clear=False):
        await self._send_command_down(
            ws, 
            self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
            channel_id=channel_id, 
            to_user_ids=self.agent.get_channel_user_list(channel_id), 
            args={'type':'url', 'image': url}
        )
    
    # only reply to the sender
    async def reply_command_text(self, data, ws, text, clear=False):
        await self._send_command_down(
            ws, 
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id = data['extra']['channel_id'], 
            to_user_ids = [data['extra']['user_id']], 
            args = {'text': text, 'clear': clear}
        )
    
    # two texts, one for sender, one for others
    async def whistle_sender_command_text(
        self, data, ws, *,
        text_to_sender, text_to_others, 
        clear_sender=False, clear_others=False
    ):
        # to sender
        await self._send_command_down(
            ws, 
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id = data['extra']['channel_id'], 
            to_user_ids = [data['extra']['user_id']], 
            args = {'text': text_to_sender, 'clear': clear_sender}
        )
        # to others
        user_ids = self.agent.get_channel_user_list(data['extra']['channel_id'])
        await self._send_command_down(
            ws, 
            self.codes.COMMAND_DOWN_DISPLAY_TEXT,
            channel_id = data['extra']['channel_id'], 
            to_user_ids = list(set(user_ids) - {data['extra']['user_id']}), 
            args = {'text': text_to_others, 'clear': clear_others}
        )

Ancestors

Methods

def add_feature(self, name, code, prompts, func=None)
Expand source code
def add_feature(self, name, code, prompts, func=None):
    self.feature_commands.append(
        {'name': name, 'code': code, 'prompts': [prompts]})
    self.prompt[code] = prompts
    if func:
        self.feature_func_map[code] = func
async def broadcast_command_image(self, channel_id, ws, url, clear=False)
Expand source code
async def broadcast_command_image(self, channel_id, ws, url, clear=False):
    await self._send_command_down(
        ws, 
        self.codes.COMMAND_DOWN_DISPLAY_IMAGE,
        channel_id=channel_id, 
        to_user_ids=self.agent.get_channel_user_list(channel_id), 
        args={'type':'url', 'image': url}
    )
async def broadcast_command_text(self, channel_id, ws, text, clear=False)
Expand source code
async def broadcast_command_text(self, channel_id, ws, text, clear=False):
    await self._send_command_down(
        ws, 
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id=channel_id, 
        to_user_ids=self.agent.get_channel_user_list(channel_id), 
        args={'text': text, 'clear': clear}
    )
async def reply_command_text(self, data, ws, text, clear=False)
Expand source code
async def reply_command_text(self, data, ws, text, clear=False):
    await self._send_command_down(
        ws, 
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id = data['extra']['channel_id'], 
        to_user_ids = [data['extra']['user_id']], 
        args = {'text': text, 'clear': clear}
    )
def set_basic_command_handle(self, code, func)
Expand source code
def set_basic_command_handle(self, code, func):
    self.basic_command_func_map[code] = func
def set_feature_handle(self, code, func)
Expand source code
def set_feature_handle(self, code, func):
    self.feature_func_map[code] = func
def set_message_handle(self, code, func)
Expand source code
def set_message_handle(self, code, func):
    self.message_func_map[code] = func
def set_notice_handle(self, code, func)
Expand source code
def set_notice_handle(self, code, func):
    self.notice_func_map[code] = func
async def whistle_sender_command_text(self, data, ws, *, text_to_sender, text_to_others, clear_sender=False, clear_others=False)
Expand source code
async def whistle_sender_command_text(
    self, data, ws, *,
    text_to_sender, text_to_others, 
    clear_sender=False, clear_others=False
):
    # to sender
    await self._send_command_down(
        ws, 
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id = data['extra']['channel_id'], 
        to_user_ids = [data['extra']['user_id']], 
        args = {'text': text_to_sender, 'clear': clear_sender}
    )
    # to others
    user_ids = self.agent.get_channel_user_list(data['extra']['channel_id'])
    await self._send_command_down(
        ws, 
        self.codes.COMMAND_DOWN_DISPLAY_TEXT,
        channel_id = data['extra']['channel_id'], 
        to_user_ids = list(set(user_ids) - {data['extra']['user_id']}), 
        args = {'text': text_to_others, 'clear': clear_others}
    )