socialization.ccs API documentation

# 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

  - JSONWebsocketActiveService

  ### Subclasses

  - WrappedGodService

  ### 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

  - JSONWebsocketPassiveService

  ### Subclasses

  - WrappedGoddessService
`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

  - BaseGodService

  ### 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

  - BaseGoddessService

  ### 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

  - BaseGodService
  - JSONWebsocketActiveService

  ### 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

  - BaseGoddessService
  - JSONWebsocketPassiveService

  ### 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}
            )