Exemple #1
0
    def queue_for_network(self):
        """ Queue message for networks, hosting or sending. """
        log = logging.getLogger(__name__)

        # check all tags of message; if they match a message network,
        # either record for hosting servers, or schedule for delivery.
        for tag in self.tags:

            # server networks offered by this server,
            # message is for a network we host
            if tag in get_ini(section='msg', key='server_tags', split=True):
                with DBProxy('{0}trans'.format(tag)) as transdb:
                    self.body = u''.join((self.body, format_origin_line()))
                    self.save()
                    transdb[self.idx] = self.idx
                log.info(
                    '[{tag}] Stored for network (msgid {self.idx}).'.format(
                        tag=tag, self=self))

            # server networks this server is a member of,
            # message is for a another network, queue for delivery
            elif tag in get_ini(section='msg', key='network_tags', split=True):
                with DBProxy('{0}queues'.format(tag)) as queuedb:
                    queuedb[self.idx] = tag
                log.info('[{tag}] Message (msgid {self.idx}) queued '
                         'for delivery'.format(tag=tag, self=self))
Exemple #2
0
    def read_configuration(self):
        # Working Folders pull from .x84 Default INI
        self.__inbound_folder = ''.join(get_ini(section='mailpacket', key='inbound', split=True))
        self.__unpack_folder = ''.join(get_ini(section='mailpacket', key='unpack', split=True))

        # read .x84 default.ini file for network info
        # build dicts for all networks and their associations
        self.add_network()
        print 'network_list: ' + ', '.join(str(x) for x in self.__network_list)

        self.add_node_address()
        for key, val in self.__node_address.items():
            print 'node_address: {key}, {value}'.format(key=key, value=val)

        self.add_export_address()
        for key, val in self.__export_address.items():
            print 'export_address: {key}, {value}'.format(key=key, value=val)

        self.add_network_areas()
        for key, val in self.__network_areas.items():
            print 'network_areas: {key}, {value}'.format(key=key, value=val)

        self.add_default_areas()
        for key, val in self.__default_areas.items():
            print 'default_areas: {key}, {value}'.format(key=key, value=val)
Exemple #3
0
    def queue_for_network(self):
        """ Queue message for networks, hosting or sending. """
        log = logging.getLogger(__name__)

        # check all tags of message; if they match a message network,
        # either record for hosting servers, or schedule for delivery.
        for tag in self.tags:

            # server networks offered by this server,
            # message is for a network we host
            if tag in get_ini(section='msg', key='server_tags', split=True):
                with DBProxy('{0}trans'.format(tag)) as transdb:
                    self.body = u''.join((self.body, format_origin_line()))
                    self.save()
                    transdb[self.idx] = self.idx
                log.info('[{tag}] Stored for network (msgid {self.idx}).'
                         .format(tag=tag, self=self))

            # server networks this server is a member of,
            # message is for a another network, queue for delivery
            elif tag in get_ini(section='msg', key='network_tags', split=True):
                with DBProxy('{0}queues'.format(tag)) as queuedb:
                    queuedb[self.idx] = tag
                log.info('[{tag}] Message (msgid {self.idx}) queued '
                         'for delivery'.format(tag=tag, self=self))
Exemple #4
0
    def __init__(self, terminal, sid, env, child_pipes, kind, addrport,
                 matrix_args, matrix_kwargs):
        # pylint: disable=R0913
        #        Too many arguments
        """ Instantiate a Session.

        Only one session may be instantiated per process.

        :param blessed.Terminal terminal: interactive terminal associated with
                                          this session.
        :param str sid: session identification string
        :param dict env: transport-negotiated environment variables, should
                         contain at least values for TERM and 'encoding'.
        :param tuple child_pipes: tuple of ``(writer, reader)``.
        :param str kind: transport description string (ssh, telnet)
        :param str addrport: transport ip address and port as string
        :param tuple matrix_args: When non-None, a tuple of positional
                                  arguments passed to the matrix script.
        :param dict matrix_kwargs: When non-None, a dictionary of keyword
                                   arguments passed to the matrix script.
        """
        self.log = logging.getLogger(__name__)

        # pylint: disable=W0603
        #        Using the global statement
        # Manage `SESSION' per-process singleton.
        global SESSION
        assert SESSION is None, 'Only one Session per process allowed'
        SESSION = self

        # public attributes
        self.terminal = terminal
        self.sid = sid
        self.env = env
        self.writer, self.reader = child_pipes
        self.kind = kind
        self.addrport = addrport

        # initialize keyboard encoding
        terminal.set_keyboard_decoder(env['encoding'])

        # private attributes
        self._script_stack = [
            # Initialize the "script stack" with the matrix script
            # using the default configuration value of `script' for all
            # connections, but preferring `script_{kind}', where `kind'
            # may be `telnet', `ssh', or any kind of supporting transport.
            Script(
                name=get_ini('matrix', 'script_{self.kind}'.format(self=self)
                             ) or get_ini('matrix', 'script'),
                args=matrix_args,
                kwargs=matrix_kwargs)
        ]
        self._connect_time = time.time()
        self._last_input_time = time.time()
        self._node = None

        # create event buffer
        self._buffer = dict()
Exemple #5
0
    def __init__(self, terminal, sid, env, child_pipes, kind, addrport,
                 matrix_args, matrix_kwargs):
        # pylint: disable=R0913
        #        Too many arguments
        """ Instantiate a Session.

        Only one session may be instantiated per process.

        :param blessed.Terminal terminal: interactive terminal associated with
                                          this session.
        :param str sid: session identification string
        :param dict env: transport-negotiated environment variables, should
                         contain at least values for TERM and 'encoding'.
        :param tuple child_pipes: tuple of ``(writer, reader)``.
        :param str kind: transport description string (ssh, telnet)
        :param str addrport: transport ip address and port as string
        :param tuple matrix_args: When non-None, a tuple of positional
                                  arguments passed to the matrix script.
        :param dict matrix_kwargs: When non-None, a dictionary of keyword
                                   arguments passed to the matrix script.
        """
        self.log = logging.getLogger(__name__)

        # pylint: disable=W0603
        #        Using the global statement
        # Manage `SESSION' per-process singleton.
        global SESSION
        assert SESSION is None, 'Only one Session per process allowed'
        SESSION = self

        # public attributes
        self.terminal = terminal
        self.sid = sid
        self.env = env
        self.writer, self.reader = child_pipes
        self.kind = kind
        self.addrport = addrport

        # initialize keyboard encoding
        terminal.set_keyboard_decoder(env['encoding'])

        # private attributes
        self._script_stack = [
            # Initialize the "script stack" with the matrix script
            # using the default configuration value of `script' for all
            # connections, but preferring `script_{kind}', where `kind'
            # may be `telnet', `ssh', or any kind of supporting transport.
            Script(
                name=get_ini('matrix', 'script_{self.kind}'.format(self=self))
                or get_ini('matrix', 'script'),
                args=matrix_args,
                kwargs=matrix_kwargs)
        ]
        self._connect_time = time.time()
        self._last_input_time = time.time()
        self._node = None

        # create event buffer
        self._buffer = dict()
Exemple #6
0
def check_anonymous_user(username):
    """ Boolean return when user is anonymous and is allowed. """
    from x84.bbs import get_ini
    matching = get_ini(section='matrix', key='anoncmds', split=True)
    allowed = get_ini(section='matrix',
                      key='enable_anonymous',
                      getter='getboolean',
                      split=False)
    return allowed and username in matching
Exemple #7
0
def check_new_user(username):
    """ Boolean return when username matches ``newcmds`` ini cfg. """
    from x84.bbs import get_ini
    matching = get_ini(section='matrix',
                       key='newcmds',
                       split=True)
    allowed = get_ini(section='nua',
                      key='allow_apply',
                      getter='getboolean')
    return allowed and username in matching
Exemple #8
0
def check_anonymous_user(username):
    """ Boolean return when user is anonymous and is allowed. """
    from x84.bbs import get_ini
    matching = get_ini(section='matrix',
                       key='anoncmds',
                       split=True)
    allowed = get_ini(section='matrix',
                      key='enable_anonymous',
                      getter='getboolean',
                      split=False)
    return allowed and username in matching
Exemple #9
0
def web_module():
    """ Expose our REST API. Run only once on server startup. """
    from x84.bbs.ini import get_ini

    # determine document root for web server
    static_root = (get_ini('web', 'document_root') or os.path.join(
        get_ini('system', 'scriptpath'), 'www-static'))
    StaticApp.static_root = static_root

    return {
        'urls': ('/www-static(/.*)?', 'static'),
        'funcs': {
            'static': StaticApp
        }
    }
Exemple #10
0
    def __init__(self, queue, event, data):
        """
        Class initializer.

        :param multiprocessing.Pipe queue: parent input end of a tty session
                                           ipc queue (``tty.master_write``).
        :param str event: database schema in form of string ``'db-schema'``
                          or ``'db=schema'``.  When ``'-'`` is used, the result
                          is returned as a single transfer. When ``'='``, an
                          iterable is yielded and the data is transfered via
                          the IPC Queue as a stream.
        :param tuple data: a dict method proxy command sequence in form of
                           ``(table, command, arguments)``.  For example,
                           ``('unnamed', 'pop', 0).
        """
        self.log = logging.getLogger(__name__)
        self.queue, self.event = queue, event
        self.table, self.cmd, self.args = data

        self.iterable, self.schema = parse_dbevent(event)
        self.filepath = get_db_filepath(self.schema)

        from x84.bbs.ini import get_ini
        self._tap_db = self.log.isEnabledFor(logging.DEBUG) and (get_ini(
            'session', 'tab_db', getter='getboolean'))

        threading.Thread.__init__(self)
Exemple #11
0
 def add_export_address(self):
     # Export {mail hub} addresses per network
     if self.is_network_empty is False:
         for net in self.__network_list:
             # Loop network list and get network section.
             self.__export_address[net] = \
                 get_ini(section=net, key='export_address', split=True)
Exemple #12
0
    def __init__(self, queue, event, data):
        """
        Class initializer.

        :param multiprocessing.Pipe queue: parent input end of a tty session
                                           ipc queue (``tty.master_write``).
        :param str event: database schema in form of string ``'db-schema'``
                          or ``'db=schema'``.  When ``'-'`` is used, the result
                          is returned as a single transfer. When ``'='``, an
                          iterable is yielded and the data is transfered via
                          the IPC Queue as a stream.
        :param tuple data: a dict method proxy command sequence in form of
                           ``(table, command, arguments)``.  For example,
                           ``('unnamed', 'pop', 0).
        """
        self.log = logging.getLogger(__name__)
        self.queue, self.event = queue, event
        self.table, self.cmd, self.args = data

        self.iterable, self.schema = parse_dbevent(event)
        self.filepath = get_db_filepath(self.schema)

        from x84.bbs.ini import get_ini
        self._tap_db = self.log.isEnabledFor(logging.DEBUG) and (
            get_ini('session', 'tab_db', getter='getboolean'))

        threading.Thread.__init__(self)
Exemple #13
0
 def add_default_areas(self):
     # No Valid area, then use default area
     if self.is_network_empty is False:
         for net in self.__network_list:
             # Loop network list and get network section.
             self.__default_areas[net] = \
                 get_ini(section=net, key='default_area', split=True)
Exemple #14
0
 def add_network_areas(self):
     # key value area to tag translations per network
     if self.is_network_empty is False:
         for net in self.__network_list:
             # Loop network list and get network section.
             self.__network_areas[net] = \
                 get_ini(section=net, key='areas', split=True)
Exemple #15
0
    def __init__(self, cmd='/bin/uname', args=(), env=None, cp437=False):
        """
        Class initializer.

        :param str cmd: full path of command to execute.
        :param tuple args: command arguments as tuple.
        :param bool cp437: When true, forces decoding of external program as
                           codepage 437.  This is the most common encoding used
                           by DOS doors.
        :param dict env: Environment variables to extend to the sub-process.
                         You should more than likely specify values for TERM,
                         PATH, HOME, and LANG.
        """
        self._session, self._term = getsession(), getterminal()
        self.cmd = cmd
        if isinstance(args, tuple):
            self.args = (self.cmd,) + args
        elif isinstance(args, list):
            self.args = [self.cmd, ] + args
        else:
            raise ValueError('args must be tuple or list')

        self.log = logging.getLogger(__name__)
        self.env = (env or {}).copy()
        self.env.update(
            {'LANG': env.get('LANG', 'en_US.UTF-8'),
             'TERM': env.get('TERM', self._term.kind),
             'PATH': env.get('PATH', get_ini('door', 'path')),
             'HOME': env.get('HOME', os.getenv('HOME')),
             'LINES': str(self._term.height),
             'COLUMNS': str(self._term.width),
             })

        self.cp437 = cp437
        self._utf8_decoder = codecs.getincrementaldecoder('utf8')()
Exemple #16
0
def web_module():
    """ Expose our REST API. Run only once on server startup. """
    from x84.bbs.ini import get_ini

    # determine document root for web server
    static_root = (get_ini('web', 'document_root')
                   or os.path.join(get_ini('system', 'scriptpath'),
                                   'www-static'))
    StaticApp.static_root = static_root

    return {
        'urls': ('/www-static(/.*)?', 'static'),
        'funcs': {
            'static': StaticApp
        }
    }
Exemple #17
0
 def add_node_address(self):
     # Node Addresses per network
     if self.is_network_empty is False:
         for net in self.__network_list:
             # Loop network list and get network section.
             self.__node_address[net] = \
                 get_ini(section=net, key='node_address', split=True)
Exemple #18
0
 def script_path(self):
     """ Base filepath folder for all scripts. """
     val = get_ini('system', 'scriptpath')
     # ensure folder exists
     assert os.path.isdir(val), (
         'configuration section [system], value scriptpath: '
         'not a folder: {!r}'.format(val))
     return val
Exemple #19
0
 def script_path(self):
     """ Base filepath folder for all scripts. """
     val = get_ini('system', 'scriptpath')
     # ensure folder exists
     assert os.path.isdir(val), (
         'configuration section [system], value scriptpath: '
         'not a folder: {!r}'.format(val))
     return val
Exemple #20
0
def get_digestpw():
    """ Returns singleton to password digest routine. """
    global FN_PASSWORD_DIGEST
    if FN_PASSWORD_DIGEST is not None:
        return FN_PASSWORD_DIGEST

    from x84.bbs.ini import get_ini
    FN_PASSWORD_DIGEST = {
        'bcrypt': _digestpw_bcrypt,
        'internal': _digestpw_internal,
        'plaintext': _digestpw_plaintext,
    }.get(get_ini('system', 'password_digest'))
    return FN_PASSWORD_DIGEST
Exemple #21
0
def get_digestpw():
    """ Returns singleton to password digest routine. """
    global FN_PASSWORD_DIGEST
    if FN_PASSWORD_DIGEST is not None:
        return FN_PASSWORD_DIGEST

    from x84.bbs.ini import get_ini
    FN_PASSWORD_DIGEST = {
        'bcrypt': _digestpw_bcrypt,
        'internal': _digestpw_internal,
        'plaintext': _digestpw_plaintext,
    }.get(get_ini('system', 'password_digest'))
    return FN_PASSWORD_DIGEST
Exemple #22
0
    def script_path(self):
        """
        Base filepath folder for all scripts.
        
        :rtype: list
        """
        scriptpath_dirs = get_ini('system', 'scriptpath', split=True)

        # ensure all specified folders exist
        for directory in scriptpath_dirs:
            assert os.path.isdir(directory), (
                'configuration section [system], value scriptpath: '
                'not a folder: {!r}'.format(directory))
        return scriptpath_dirs
Exemple #23
0
    def script_path(self):
        """
        Base filepath folder for all scripts.
        
        :rtype: list
        """
        scriptpath_dirs = get_ini('system', 'scriptpath', split=True)

        # ensure all specified folders exist
        for directory in scriptpath_dirs:
            assert os.path.isdir(directory), (
                'configuration section [system], value scriptpath: '
                'not a folder: {!r}'.format(directory))
        return scriptpath_dirs
Exemple #24
0
    def __init__(self, schema, table='unnamed', use_session=True):
        """
        Class constructor.

        :param str scheme: database key, becomes basename of .sqlite3 file.
        :param str table: optional database table.
        :param bool use_session: Whether iterable returns should be sent over
                                 an IPC pipe (client is a
                                 :class:`x84.bbs.session.Session` instance),
                                 or returned directly (such as used by the main
                                 thread engine components.)
        """
        self.log = logging.getLogger(__name__)
        self.schema = schema
        self.table = table
        self._tap_db = get_ini('session', 'tab_db', getter='getboolean')
        self.session = use_session and getsession()
Exemple #25
0
    def __init__(self, queue, event, data):
        """ Arguments:
              queue: parent input end of multiprocessing.Queue()
              event: database schema in form of string 'db-schema' or
                  'db=schema'. When '-' is used, the result is returned as a
                  single transfer. When '=', an iterable is yielded and the
                  data is transfered via the IPC Queue as a stream.
        """
        self.log = logging.getLogger(__name__)
        self.queue, self.event = queue, event
        self.table, self.cmd, self.args = data

        self.iterable, self.schema = parse_dbevent(event)
        self.filepath = get_db_filepath(self.schema)
        self._tap_db = get_ini('session', 'tab_db', getter='getboolean')

        threading.Thread.__init__(self)
Exemple #26
0
    def __init__(self, schema, table='unnamed', use_session=True):
        """
        Class initializer.

        :param str scheme: database key, becomes basename of .sqlite3 file.
        :param str table: optional database table.
        :param bool use_session: Whether iterable returns should be sent over
                                 an IPC pipe (client is a
                                 :class:`x84.bbs.session.Session` instance),
                                 or returned directly (such as used by the main
                                 thread engine components.)
        """
        self.log = logging.getLogger(__name__)
        self.schema = schema
        self.table = table
        self._tap_db = get_ini('session', 'tab_db', getter='getboolean')

        from x84.bbs.session import getsession
        self._session = use_session and getsession()
Exemple #27
0
    def __init__(self, cmd='/bin/uname', args=(), env_lang='en_US.UTF-8',
                 env_term=None, env_path=None, env_home=None, cp437=False,
                 env=None):
        """
        Class constructor.

        :param str cmd: full path of command to execute.
        :param tuple args: command arguments as tuple.
        :param str env_lang: exported as environment variable ``LANG``.
        :param str env_term: exported as environment variable ``TERM``.  When
                             unspecified, it is determined by the same
                             TERM value the original ``blessed.Terminal``
                             instance is used.
        :param str env_path: exported as environment variable ``PATH``.
                             When None (default), the .ini ``env_path``
                             value of section ``[door]`` is
        :param str env_home: exported as environment variable ``HOME``.
                             When env_home is ``None``, the environment
                             value of the main process is used.
        :param bool cp437: When true, forces decoding of external program as
                           codepage 437.  This is the most common encoding used
                           by DOS doors.
        :param dict env: Additional environment variables to extend to the
                         sub-process.
        """
        self._session, self._term = getsession(), getterminal()
        self.cmd = cmd
        if isinstance(args, tuple):
            self.args = (self.cmd,) + args
        elif isinstance(args, list):
            self.args = [self.cmd, ] + args
        else:
            raise ValueError('args must be tuple or list')
        self.env_lang = env_lang
        self.env_term = env_term or self._term.kind
        self.env_path = env_path or get_ini('door', 'path')
        self.env_home = env_home or os.getenv('HOME')
        self.env = env or {}
        self.cp437 = cp437
        self._utf8_decoder = codecs.getincrementaldecoder('utf8')()
Exemple #28
0
 def tap_input(self):
     """ Whether keyboard input should be logged (bool). """
     return get_ini('session', 'tap_input', getter='getboolean')
Exemple #29
0
def get_db_filepath(schema):
    """ Return filesystem path of given database ``schema``. """
    from x84.bbs.ini import get_ini
    folder = get_ini('system', 'datapath')
    return os.path.join(folder, '{0}.sqlite3'.format(schema))
Exemple #30
0
def check_bye_user(username):
    """ Boolean return when username matches ``byecmds`` in ini cfg. """
    from x84.bbs import get_ini
    matching = get_ini(section='matrix', key='byecmds', split=True)
    return matching and username in matching
Exemple #31
0
def get_origin_line():
    """ Return ``origin`` configuration item of ``[msg]`` section. """
    return get_ini(section='msg',
                   key='origin_line') or ('Sent from {0}'.format(
                       get_ini(section='system', key='bbsname')))
Exemple #32
0
def get_db_filepath(schema):
    folder = get_ini('system', 'datapath')
    return os.path.join(folder, '{0}.sqlite3'.format(schema))
Exemple #33
0
def check_new_user(username):
    """ Boolean return when username matches ``newcmds`` ini cfg. """
    from x84.bbs import get_ini
    matching = get_ini(section='matrix', key='newcmds', split=True)
    allowed = get_ini(section='nua', key='allow_apply', getter='getboolean')
    return allowed and username in matching
Exemple #34
0
 def tap_output(self):
     """ Whether screen output should be logged (bool). """
     return get_ini('session', 'tap_output', getter='getboolean')
Exemple #35
0
 def tap_input(self):
     """ Whether keyboard input should be logged (bool). """
     return get_ini('session', 'tap_input', getter='getboolean')
Exemple #36
0
def showart(filepattern,
            encoding=None,
            auto_mode=True,
            center=False,
            poll_cancel=False,
            msg_cancel=None,
            force=False):
    """
    Yield unicode sequences for any given ANSI Art (of art_encoding).

    Effort is made to parse SAUCE data, translate input to unicode, and trim
    artwork too large to display.  If ``poll_cancel`` is not ``False``,
    represents time as float for each line to block for keypress -- if any is
    received, then iteration ends and ``msg_cancel`` is displayed as last line
    of art.

    If you provide no ``encoding``, the piece encoding will be based on either
    the encoding in the SAUCE record, the configured default or the default
    fallback ``CP437`` encoding.

    Alternate codecs are available if you provide the ``encoding`` argument.
    For example, if you want to show an Amiga style ASCII art file::

        >>> from x84.bbs import echo, showart
        >>> for line in showart('test.asc', 'topaz'):
        ...     echo(line)

    The ``auto_mode`` flag will, if set, only respect the selected encoding if
    the active session is UTF-8 capable.

    If ``center`` is set to ``True``, the piece will be centered respecting the
    current terminal's width.

    If ``force`` is set to true then the artwork will be displayed even if it's
    wider than the screen.

    """
    # pylint: disable=R0913,R0914
    #         Too many arguments
    #         Too many local variables
    term = getterminal()

    # When the given artfile pattern's folder is not absolute, nor relative to
    # our cwd, build a relative position of the folder by the calling module's
    # containing folder.  This only works for subdirectories (like 'art/').
    _folder = os.path.dirname(filepattern)
    if not (_folder.startswith(os.path.sep) or os.path.isdir(_folder)):
        # On occasion, after a general exception in a script, re-calling the
        # same script may cause yet another exception, HERE.  The 2nd call is
        # fine though; this only would effect a developer.
        #
        # Just try again.
        caller_module = inspect.stack()[1][1]
        rel_folder = os.path.dirname(caller_module)
        if _folder:
            rel_folder = os.path.join(rel_folder, _folder)
        if os.path.isdir(rel_folder):
            filepattern = os.path.join(rel_folder,
                                       os.path.basename(filepattern))

    # Open the piece
    try:
        filename = os.path.relpath(random.choice(glob.glob(filepattern)))
    except IndexError:
        filename = None

    if filename is None:
        yield u''.join((
            term.bold_red(u'-- '),
            u'no files matching {0}'.format(filepattern),
            term.bold_red(u' --'),
        ))
        return

    file_basename = os.path.basename(filename)

    # Parse the piece
    parsed = SAUCE(filename)

    # If no explicit encoding is given, we go through a couple of steps to
    # resolve the possible file encoding:
    if encoding is None:
        # 1. See if the SAUCE record has a font we know about, it's in the
        #    filler
        if parsed.record and parsed.filler_str in SAUCE_FONT_MAP:
            encoding = SAUCE_FONT_MAP[parsed.filler_str]

        # 2. Get the system default art encoding,
        #    or fall-back to cp437
        else:
            encoding = get_ini('system', 'art_utf8_codec') or 'cp437'

    # If auto_mode is enabled, we'll only use the input encoding on UTF-8
    # capable terminals, because our codecs do not know how to "transcode"
    # between the various encodings.
    if auto_mode:

        def _decode(what):
            # pylint: disable=C0111
            #         Missing function docstring (col 8)
            session = getsession()
            if session.encoding == 'utf8':
                return what.decode(encoding)
            elif session.encoding == 'cp437':
                return what.decode('cp437')
            else:
                return what

    # If auto_mode is disabled, we'll just respect whatever input encoding was
    # selected before
    else:
        _decode = lambda what: what.decode(encoding)

    # For wide terminals, center piece on screen using cursor movement
    # when center=True.
    padding = u''
    if center and term.width > 81:
        padding = term.move_x((term.width / 2) - 40)
    lines = _decode(parsed.data).splitlines()
    for idx, line in enumerate(lines):

        if poll_cancel is not False and term.inkey(poll_cancel):
            # Allow slow terminals to cancel by pressing a keystroke
            msg_cancel = msg_cancel or u''.join((
                term.normal,
                term.bold_black(u'-- '),
                u'canceled {0} by input'.format(os.path.basename(filename)),
                term.bold_black(u' --'),
            ))
            yield u'\r\n' + term.center(msg_cancel).rstrip() + u'\r\n'
            return

        line_length = term.length(line.rstrip())

        if force is False and not padding and term.width < line_length:
            # if the artwork is too wide and force=False, simply stop displaying it.
            msg_too_wide = u''.join((
                term.normal,
                term.bold_black(u'-- '),
                (u'canceled {0}, too wide:: {1}'.format(
                    file_basename, line_length)),
                term.bold_black(u' --'),
            ))
            yield (u'\r\n' + term.center(msg_too_wide).rstrip() + u'\r\n')
            return
        if idx == len(lines) - 1:
            # strip DOS end of file (^Z)
            line = line.rstrip('\x1a')
            if not line.strip():
                break
        yield padding + line + u'\r\n'
    yield term.normal
Exemple #37
0
 def tap_input(self):
     """ Whether keyboard input should be logged (bool). """
     return get_ini("session", "tap_input", getter="getboolean")
Exemple #38
0
 def sysopname(self):
     """ name of sysop. """
     return get_ini('system', 'sysop') or u''
Exemple #39
0
 def systemname(self):
     """ BBS System name. """
     return get_ini('system', 'software') or 'x/84'
Exemple #40
0
 def tap_output(self):
     """ Whether screen output should be logged (bool). """
     return get_ini("session", "tap_output", getter="getboolean")
Exemple #41
0
 def tap_output(self):
     """ Whether screen output should be logged (bool). """
     return get_ini('session', 'tap_output', getter='getboolean')
Exemple #42
0
 def show_traceback(self):
     """ Whether traceback errors should be displayed to user (bool). """
     return get_ini('system', 'show_traceback', getter='getboolean')
Exemple #43
0
 def show_traceback(self):
     """ Whether traceback errors should be displayed to user (bool). """
     return get_ini('system', 'show_traceback', getter='getboolean')
Exemple #44
0
def get_db_filepath(schema):
    """ Return filesystem path of given database ``schema``. """
    from x84.bbs.ini import get_ini
    folder = get_ini('system', 'datapath')
    return os.path.join(folder, '{0}.sqlite3'.format(schema))
Exemple #45
0
 def add_network(self):
     # Everything is built from the initial network address
     self.__network_list = \
         get_ini(section='fido_networks', key='network_tags', split=True)
Exemple #46
0
 def show_traceback(self):
     """ Whether traceback errors should be displayed to user (bool). """
     return get_ini("system", "show_traceback", getter="getboolean")
Exemple #47
0
__author__ = u'haliphax <https://github.com/haliphax>'

# stdlib
import json
from datetime import date

# 3rd party
import requests

# local
from x84.bbs import getterminal, getsession, echo, Lightbar, DBProxy, getch
from x84.bbs.ini import get_ini
from common import prompt_pager

# ini settings
PROMPT_LOWLIGHT_COLOR = get_ini('horoscope', 'prompt_lowlight_color') or \
    u'bright_blue'
PROMPT_HIGHLIGHT_COLOR = get_ini('horoscope', 'prompt_highlight_color') or \
    u'bold_bright_white'
LIGHTBAR_BORDER_COLOR = get_ini('horoscope', 'lightbar_border_color') or \
    u'blue'
LIGHTBAR_LOWLIGHT_COLOR = get_ini('horoscope', 'lightbar_lowlight_color') or \
    u'white'
LIGHTBAR_HIGHLIGHT_COLOR = get_ini('horoscope', 'lightbar_highlight_color') or \
    u'bright_white_on_blue'
HEADER_HIGHLIGHT_COLOR = get_ini('horoscope', 'header_highlight_color') or \
    u'white'
HEADER_LOWLIGHT_COLOR = get_ini('horoscope', 'header_lowlight_color') or \
    u'blue'
TEXT_HIGHLIGHT_COLOR = get_ini('horoscope', 'text_highlight_color') or \
    u'bold_underline_bright_white'
Exemple #48
0
def showart(filepattern, encoding=None, auto_mode=True, center=False,
            poll_cancel=False, msg_cancel=None, quiet=False):
    """
    Yield unicode sequences for any given ANSI Art (of art_encoding).

    Effort is made to parse SAUCE data, translate input to unicode, and trim
    artwork too large to display.  If ``poll_cancel`` is not ``False``,
    represents time as float for each line to block for keypress -- if any is
    received, then iteration ends and ``msg_cancel`` is displayed as last line
    of art.

    If you provide no ``encoding``, the piece encoding will be based on either
    the encoding in the SAUCE record, the configured default or the default
    fallback ``CP437`` encoding.

    Alternate codecs are available if you provide the ``encoding`` argument.
    For example, if you want to show an Amiga style ASCII art file::

        >>> from x84.bbs import echo, showart
        >>> for line in showart('test.asc', 'topaz'):
        ...     echo(line)

    The ``auto_mode`` flag will, if set, only respect the selected encoding if
    the active session is UTF-8 capable.

    If ``center`` is set to ``True``, the piece will be centered respecting the
    current terminal's width.

    """
    # pylint: disable=R0913,R0914
    #         Too many arguments
    #         Too many local variables
    term = getterminal()

    # When the given artfile pattern's folder is not absolute, nor relative to
    # our cwd, build a relative position of the folder by the calling module's
    # containing folder.  This only works for subdirectories (like 'art/').
    _folder = os.path.dirname(filepattern)
    if not (_folder.startswith(os.path.sep) or os.path.isdir(_folder)):
        # On occasion, after a general exception in a script, re-calling the
        # same script may cause yet another exception, HERE.  The 2nd call is
        # fine though; this only would effect a developer.
        #
        # Just try again.
        caller_module = inspect.stack()[1][1]
        rel_folder = os.path.dirname(caller_module)
        if _folder:
            rel_folder = os.path.join(rel_folder, _folder)
        if os.path.isdir(rel_folder):
            filepattern = os.path.join(
                rel_folder,
                os.path.basename(filepattern))

    # Open the piece
    try:
        filename = os.path.relpath(random.choice(glob.glob(filepattern)))
    except IndexError:
        filename = None

    if filename is None:
        yield u''.join((
            term.bold_red(u'-- '),
            u'no files matching {0}'.format(filepattern),
            term.bold_red(u' --'),
        ))
        return

    file_basename = os.path.basename(filename)

    # Parse the piece
    parsed = SAUCE(filename)

    # If no explicit encoding is given, we go through a couple of steps to
    # resolve the possible file encoding:
    if encoding is None:
        # 1. See if the SAUCE record has a font we know about, it's in the
        #    filler
        if parsed.record and parsed.filler_str in SAUCE_FONT_MAP:
            encoding = SAUCE_FONT_MAP[parsed.filler_str]

        # 2. Get the system default art encoding,
        #    or fall-back to cp437
        else:
            encoding = get_ini('system', 'art_utf8_codec') or 'cp437'

    # If auto_mode is enabled, we'll only use the input encoding on UTF-8
    # capable terminals, because our codecs do not know how to "transcode"
    # between the various encodings.
    if auto_mode:
        def _decode(what):
            # pylint: disable=C0111
            #         Missing function docstring (col 8)
            session = getsession()
            if session.encoding == 'utf8':
                return what.decode(encoding)
            elif session.encoding == 'cp437':
                return what.decode('cp437')
            else:
                return what

    # If auto_mode is disabled, we'll just respect whatever input encoding was
    # selected before
    else:
        _decode = lambda what: what.decode(encoding)

    # For wide terminals, center piece on screen using cursor movement
    # when center=True.
    padding = u''
    if center and term.width > 81:
        padding = term.move_x((term.width / 2) - 40)
    lines = _decode(parsed.data).splitlines()
    for idx, line in enumerate(lines):

        if poll_cancel is not False and term.inkey(poll_cancel):
            # Allow slow terminals to cancel by pressing a keystroke
            msg_cancel = msg_cancel or u''.join(
                (term.normal,
                 term.bold_black(u'-- '),
                 u'canceled {0} by input'.format(os.path.basename(filename)),
                 term.bold_black(u' --'),
                 ))
            yield u'\r\n' + term.center(msg_cancel).rstrip() + u'\r\n'
            return

        line_length = term.length(line.rstrip())

        if not padding and term.width < line_length:
            # if the artwork is too wide, simply stop displaying it.
            if not quiet:
                msg_too_wide = u''.join(
                    (term.normal,
                     term.bold_black(u'-- '),
                     (u'canceled {0}, too wide:: {1}'
                      .format(file_basename, line_length)),
                     term.bold_black(u' --'),
                     ))
                yield (u'\r\n' +
                       term.center(msg_too_wide).rstrip() +
                       u'\r\n')
            return
        if idx == len(lines) - 1:
            # strip DOS end of file (^Z)
            line = line.rstrip('\x1a')
            if not line.strip():
                break
        yield padding + line + u'\r\n'
    yield term.normal
Exemple #49
0
    def save(self, send_net=True, ctime=None):
        """
        Save message to database, recording 'tags' db.

        As a side-effect, it may queue message for delivery to
        external systems, when configured.
        """
        log = logging.getLogger(__name__)
        session = getsession()
        use_session = bool(session is not None)
        new = self.idx is None or self._stime is None

        # persist message record to MSGDB
        with DBProxy(MSGDB, use_session=use_session) as db_msg:
            if new:
                self.idx = max(map(int, db_msg.keys()) or [-1]) + 1
                if ctime is not None:
                    self._ctime = self._stime = ctime
                else:
                    self._stime = datetime.datetime.now()
                new = True
            db_msg['%d' % (self.idx, )] = self

        # persist message idx to TAGDB
        with DBProxy(TAGDB, use_session=use_session) as db_tag:
            for tag in db_tag.keys():
                msgs = db_tag[tag]
                if tag in self.tags and self.idx not in msgs:
                    msgs.add(self.idx)
                    db_tag[tag] = msgs
                    log.debug("msg {self.idx} tagged '{tag}'".format(self=self,
                                                                     tag=tag))
                elif tag not in self.tags and self.idx in msgs:
                    msgs.remove(self.idx)
                    db_tag[tag] = msgs
                    log.info("msg {self.idx} removed tag '{tag}'".format(
                        self=self, tag=tag))
            for tag in [_tag for _tag in self.tags if _tag not in db_tag]:
                db_tag[tag] = set([self.idx])

        # persist message as child to parent;
        assert self.parent not in self.children, ('circular reference',
                                                  self.parent, self.children)
        if self.parent is not None:
            try:
                parent_msg = get_msg(self.parent)
            except KeyError:
                log.warn('Child message {0}.parent = {1}: '
                         'parent does not exist!'.format(
                             self.idx, self.parent))
            else:
                if self.idx != parent_msg.idx:
                    parent_msg.children.add(self.idx)
                    parent_msg.save()
                else:
                    log.error('Parent idx same as message idx; stripping')
                    self.parent = None
                    with db_msg:
                        db_msg['%d' % (self.idx)] = self

        # persist message record to PRIVDB
        if 'public' not in self.tags:
            with DBProxy(PRIVDB, use_session=use_session) as db_priv:
                db_priv[self.recipient] = (db_priv.get(self.recipient, set())
                                           | set([self.idx]))

        # if either any of 'server_tags' or 'network_tags' are enabled,
        # then queue for potential delivery.
        if send_net and new and (get_ini(section='msg', key='network_tags')
                                 or get_ini(section='msg', key='server_tags')):
            self.queue_for_network()

        log.info(u"saved {new} {public_or_private} {message_or_reply}"
                 u", addressed to '{self.recipient}'.".format(
                     new='new ' if new else '',
                     public_or_private=('public' if 'public' in self.tags else
                                        'private'),
                     message_or_reply=('message'
                                       if self.parent is None else 'reply'),
                     self=self))