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))
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)
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))
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()
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()
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
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
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 } }
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)
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)
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)
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)
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)
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')()
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 } }
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)
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
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
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
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()
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)
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()
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')()
def tap_input(self): """ Whether keyboard input should be logged (bool). """ return get_ini('session', 'tap_input', getter='getboolean')
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))
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
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')))
def get_db_filepath(schema): folder = get_ini('system', 'datapath') return os.path.join(folder, '{0}.sqlite3'.format(schema))
def tap_output(self): """ Whether screen output should be logged (bool). """ return get_ini('session', 'tap_output', getter='getboolean')
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
def tap_input(self): """ Whether keyboard input should be logged (bool). """ return get_ini("session", "tap_input", getter="getboolean")
def sysopname(self): """ name of sysop. """ return get_ini('system', 'sysop') or u''
def systemname(self): """ BBS System name. """ return get_ini('system', 'software') or 'x/84'
def tap_output(self): """ Whether screen output should be logged (bool). """ return get_ini("session", "tap_output", getter="getboolean")
def show_traceback(self): """ Whether traceback errors should be displayed to user (bool). """ return get_ini('system', 'show_traceback', getter='getboolean')
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)
def show_traceback(self): """ Whether traceback errors should be displayed to user (bool). """ return get_ini("system", "show_traceback", getter="getboolean")
__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'
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
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))