Пример #1
0
 def format_msg(reader, idx):
     """ Format message of index ``idx`` into Pager instance ``reader``. """
     msg = get_msg(idx)
     sent = msg.stime.strftime(TIME_FMT)
     to_attr = term.bold_green if (
         msg.recipient == session.user.handle) else term.underline
     ucs = u'\r\n'.join((
         (u''.join((
             term.yellow('fROM: '),
             (u'%s' % term.bold(msg.author, )).rjust(len_author),
             u' ' * (reader.visible_width - (len_author + len(sent))),
             sent,
         ))),
         u''.join((
             term.yellow('tO: '),
             to_attr((
                 u'%s' % to_attr(msg.recipient, )
             ).rjust(len_author) if msg.recipient is not None else u'All'),
         )),
         (u'\r\n'.join((term.wrap(
             term.yellow('tAGS: ') + (u'%s ' % (term.bold(','), )).join(([
                 term.bold_red(_tag)
                 if _tag in SEARCH_TAGS else term.yellow(_tag)
                 for _tag in msg.tags
             ])),
             reader.visible_width,
             subsequent_indent=u' ' * 6)))),
         (term.yellow_underline(
             (u'SUbj: %s' % (msg.subject, )).ljust(reader.visible_width))),
         u'',
         (msg.body),
     ))
     return ucs
Пример #2
0
 def format_msg(reader, idx):
     """ Format message of index ``idx`` into Pager instance ``reader``. """
     msg = get_msg(idx)
     sent = msg.stime.strftime(TIME_FMT)
     to_attr = term.bold_green if (
         msg.recipient == session.user.handle) else term.underline
     ucs = u'\r\n'.join((
         (u''.join((
             term.yellow('fROM: '),
             (u'%s' % term.bold(msg.author,)).rjust(len_author),
             u' ' * (reader.visible_width - (len_author + len(sent))),
             sent,))),
         u''.join((
             term.yellow('tO: '),
             to_attr((u'%s' % to_attr(msg.recipient,)).rjust(len_author)
                     if msg.recipient is not None else u'All'),)),
         (Ansi(
             term.yellow('tAGS: ')
             + (u'%s ' % (term.bold(','),)).join((
                 [term.bold_red(_tag)
                     if _tag in SEARCH_TAGS
                     else term.yellow(_tag)
                     for _tag in msg.tags]))).wrap(
                         reader.visible_width,
                         indent=u'      ')),
         (term.yellow_underline(
             (u'SUbj: %s' % (msg.subject,)).ljust(reader.visible_width)
         )),
         u'', (msg.body),))
     return ucs
Пример #3
0
def allow_tag(session, idx):
    """
    Whether user is allowed to tag a message.

    :rtype: bool

    A user can tag a message if the given session's user is:

    * the message author or recipient.
    * a member of sysop or moderator group.
    * a member of any existing tag-matching user group.
    """
    moderated = get_ini('msg', 'moderated_tags', getter='getboolean')
    tag_moderators = set(get_ini('msg', 'tag_moderators', split=True))
    if not moderated and 'sysop' in session.user.groups:
        return True

    elif moderated and (tag_moderators | session.user.groups):
        # tags are moderated, but user is one of the moderator groups
        return True

    msg = get_msg(idx)
    if session.user.handle in (msg.recipient, msg.author):
        return True

    for tag in msg.tags:
        if tag in session.user.groups:
            return True
    return False
Пример #4
0
def allow_tag(session, idx):
    """
    Whether user is allowed to tag a message.

    :rtype: bool

    A user can tag a message if the given session's user is:

    * the message author or recipient.
    * a member of sysop or moderator group.
    * a member of any existing tag-matching user group.
    """
    moderated = get_ini('msg', 'moderated_tags', getter='getboolean')
    tag_moderators = set(get_ini('msg', 'tag_moderators', split=True))
    if not moderated and 'sysop' in session.user.groups:
        return True

    elif moderated and (tag_moderators | session.user.groups):
        # tags are moderated, but user is one of the moderator groups
        return True

    msg = get_msg(idx)
    if session.user.handle in (msg.recipient, msg.author):
        return True

    for tag in msg.tags:
        if tag in session.user.groups:
            return True
    return False
Пример #5
0
 def head(msg, depth=0, maxdepth=reply_depth):
     """ This recursive routine finds the 'head' message
         of any relationship, up to maxdepth.
     """
     if (depth <= maxdepth and hasattr(msg, 'parent')
             and msg.parent is not None):
         return head(get_msg(msg.parent), depth + 1, maxdepth)
     return msg.idx, depth
Пример #6
0
 def head(msg, depth=0, maxdepth=reply_depth):
     """ This recursive routine finds the 'head' message
         of any relationship, up to maxdepth.
     """
     if (depth <= maxdepth
             and hasattr(msg, 'parent')
             and msg.parent is not None):
         return head(get_msg(msg.parent), depth + 1, maxdepth)
     return msg.idx, depth
Пример #7
0
    def get_header(msgs_idx):
        """
        Return list of tuples, (idx, unicodestring), suitable for Lightbar.
        """
        import datetime
        msg_list = list()
        thread_indent = lambda depth: (term.red(
            (indent_start + (indent * depth) + indent_end)) if depth else u'')

        def head(msg, depth=0, maxdepth=reply_depth):
            """ This recursive routine finds the 'head' message
                of any relationship, up to maxdepth.
            """
            if (depth <= maxdepth and hasattr(msg, 'parent')
                    and msg.parent is not None):
                return head(get_msg(msg.parent), depth + 1, maxdepth)
            return msg.idx, depth

        for idx in msgs_idx:
            msg = get_msg(idx)
            author, subj = msg.author, msg.subject
            tm_ago = (datetime.datetime.now() - msg.stime).total_seconds()
            # pylint: disable=W0631
            #         Using possibly undefined loop variable 'idx'
            attr = lambda arg: (term.bold_green(arg)
                                if (not idx in ALREADY_READ and msg.recipient
                                    == session.user.handle) else term.red(arg)
                                if not idx in ALREADY_READ else term.yellow(arg
                                                                            ))
            status = [
                u'U' if not idx in ALREADY_READ else u' ',
                u'D' if idx in DELETED else u' ',
            ]
            row_txt = u'%s %s %s %s %s%s ' % (
                u''.join(status),
                attr(str(idx).rjust(len_idx)),
                attr(author.ljust(len_author)),
                (timeago(tm_ago)).rjust(len_ago),
                attr(u'ago'),
                term.bold_black(':'),
            )
            msg_list.append((head(msg), idx, row_txt, subj))
        msg_list.sort(reverse=True)
        return [(idx, row_txt + thread_indent(depth) + subj)
                for (_threadid, depth), idx, row_txt, subj in msg_list]
Пример #8
0
def display_message(session, term, msg_index, colors):
    """ Format message of index ``idx``. """
    color_handle = lambda handle: (colors["highlight"](handle) if handle == session.user.handle else handle)
    msg = get_msg(msg_index)
    txt_sent = msg.stime.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(dateutil.tz.tzutc()).strftime(TIME_FMT)
    txt_sentago = colors["highlight"](timeago((datetime.datetime.now() - msg.stime).total_seconds()).strip())
    txt_to = color_handle(msg.recipient)
    txt_private = colors["highlight"](" (private)") if not "public" in msg.tags else u""
    txt_from = color_handle(msg.author)
    txt_tags = u", ".join((quote(tag, colors) for tag in msg.tags))
    txt_subject = colors["highlight"](msg.subject)
    txt_body = decode_pipe(msg.body)
    txt_breaker = ("-" if session.encoding == "ansi" else u"\u2500") * min(80, term.width)
    msg_txt = (
        u"\r\n{txt_breaker}\r\n"
        u"   from: {txt_from}\r\n"
        u"     to: {txt_to}{txt_private}\r\n"
        u"   sent: {txt_sent} ({txt_sentago} ago)\r\n"
        u"   tags: {txt_tags}\r\n"
        u"subject: {txt_subject}\r\n"
        u"\r\n"
        u"{txt_body}\r\n".format(
            txt_breaker=txt_breaker,
            txt_from=txt_from,
            txt_to=txt_to,
            txt_sent=txt_sent,
            txt_sentago=txt_sentago,
            txt_tags=txt_tags,
            txt_subject=txt_subject,
            txt_body=txt_body,
            txt_private=txt_private,
        )
    )

    do_mark_as_read(session, [msg_index])

    prompt_pager(
        content=msg_txt.splitlines(),
        line_no=0,
        width=min(80, term.width),
        colors=colors,
        breaker=u"- ",
        end_prompt=False,
        break_long_words=True,
    )
Пример #9
0
    def get_header(msgs_idx):
        """
        Return list of tuples, (idx, unicodestring), suitable for Lightbar.
        """
        import datetime
        msg_list = list()
        thread_indent = lambda depth: (term.red(
            (indent_start + (indent * depth) + indent_end))
            if depth else u'')

        def head(msg, depth=0, maxdepth=reply_depth):
            """ This recursive routine finds the 'head' message
                of any relationship, up to maxdepth.
            """
            if (depth <= maxdepth
                    and hasattr(msg, 'parent')
                    and msg.parent is not None):
                return head(get_msg(msg.parent), depth + 1, maxdepth)
            return msg.idx, depth

        for idx in msgs_idx:
            msg = get_msg(idx)
            author, subj = msg.author, msg.subject
            tm_ago = (datetime.datetime.now() - msg.stime).total_seconds()
            # pylint: disable=W0631
            #         Using possibly undefined loop variable 'idx'
            attr = lambda arg: (
                term.bold_green(arg) if (
                    not idx in ALREADY_READ
                    and msg.recipient == session.user.handle) else
                term.red(arg) if not idx in ALREADY_READ
                else term.yellow(arg))
            status = [u'U' if not idx in ALREADY_READ else u' ',
                      u'D' if idx in DELETED else u' ', ]
            row_txt = u'%s %s %s %s %s%s ' % (
                u''.join(status),
                attr(str(idx).rjust(len_idx)),
                attr(author.ljust(len_author)),
                (timeago(tm_ago)).rjust(len_ago),
                attr(u'ago'),
                term.bold_black(':'),)
            msg_list.append((head(msg), idx, row_txt, subj))
        msg_list.sort()
        return [(idx, row_txt + thread_indent(depth) + subj)
                for (_threadid, depth), idx, row_txt, subj in msg_list]
Пример #10
0
def create_reply_message(session, idx):
    """ Given a message ``idx``, create and return a replying message. """
    parent_msg = get_msg(idx)
    msg = Msg()
    msg.parent = parent_msg.idx

    # flip from/to
    msg.recipient = parent_msg.author
    msg.author = session.user.handle

    # quote message body
    msg.body = quote_body(parent_msg)

    # duplicate subject and tags
    msg.subject = parent_msg.subject
    msg.tags = parent_msg.tags

    return msg
Пример #11
0
def create_reply_message(session, idx):
    """ Given a message ``idx``, create and return a replying message. """
    parent_msg = get_msg(idx)
    msg = Msg()
    msg.parent = parent_msg.idx

    # flip from/to
    msg.recipient = parent_msg.author
    msg.author = session.user.handle

    # quote message body
    msg.body = quote_body(parent_msg)

    # duplicate subject and tags
    msg.subject = parent_msg.subject
    msg.tags = parent_msg.tags

    return msg
Пример #12
0
def allow_tag(idx):
    """
    Returns true if user is allowed to 't'ag message at idx:
        * sysop and moderator
        * author or recipient
        * a member of any message tag matching user group
    """
    from x84.bbs import getsession, get_msg
    session = getsession()
    if ('sysop' in session.user.groups or 'moderator' in session.user.groups):
        return True
    msg = get_msg(idx)
    if session.user.handle in (msg.recipient, msg.author):
        return True
    for tag in msg.tags:
        if tag in session.user.groups:
            return True
    return False
Пример #13
0
def display_message(session, term, msg_index, colors):
    """ Format message of index ``idx``. """
    color_handle = lambda handle: (colors['highlight'](handle) if handle ==
                                   session.user.handle else handle)
    msg = get_msg(msg_index)
    txt_sent = msg.stime.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
        dateutil.tz.tzutc()).strftime(TIME_FMT)
    txt_sentago = colors['highlight'](timeago(
        (datetime.datetime.now() - msg.stime).total_seconds()).strip())
    txt_to = color_handle(msg.recipient)
    txt_private = (colors['highlight'](' (private)')
                   if not 'public' in msg.tags else u'')
    txt_from = color_handle(msg.author)
    txt_tags = u', '.join((quote(tag, colors) for tag in msg.tags))
    txt_subject = colors['highlight'](msg.subject)
    txt_body = decode_pipe(msg.body)
    txt_breaker = ('-' if session.encoding == 'ansi' else u'\u2500') * min(
        80, term.width)
    msg_txt = (u'\r\n{txt_breaker}\r\n'
               u'   from: {txt_from}\r\n'
               u'     to: {txt_to}{txt_private}\r\n'
               u'   sent: {txt_sent} ({txt_sentago} ago)\r\n'
               u'   tags: {txt_tags}\r\n'
               u'subject: {txt_subject}\r\n'
               u'\r\n'
               u'{txt_body}\r\n'.format(txt_breaker=txt_breaker,
                                        txt_from=txt_from,
                                        txt_to=txt_to,
                                        txt_sent=txt_sent,
                                        txt_sentago=txt_sentago,
                                        txt_tags=txt_tags,
                                        txt_subject=txt_subject,
                                        txt_body=txt_body,
                                        txt_private=txt_private))

    do_mark_as_read(session, [msg_index])

    prompt_pager(content=msg_txt.splitlines(),
                 line_no=0,
                 width=min(80, term.width),
                 colors=colors,
                 breaker=u'- ',
                 end_prompt=False,
                 break_long_words=True)
Пример #14
0
def allow_tag(idx):
    """
    Returns true if user is allowed to 't'ag message at idx:
        * sysop and moderator
        * author or recipient
        * a member of any message tag matching user group
    """
    from x84.bbs import getsession, get_msg
    session = getsession()
    if ('sysop' in session.user.groups
            or 'moderator' in session.user.groups):
        return True
    msg = get_msg(idx)
    if session.user.handle in (msg.recipient, msg.author):
        return True
    for tag in msg.tags:
        if tag in session.user.groups:
            return True
    return False
Пример #15
0
def read_messages(msgs, title, currentpage, totalpages, threadid, cachetime):
    """
    Provide message reader UI given message list ``msgs``,
    with new messages in list ``new``.
    """
    # pylint: disable=R0914,R0912,R0915
    #         Too many local variables
    #         Too many branches
    #         Too many statements
    from x84.bbs import timeago, get_msg, getterminal, echo, gosub
    from x84.bbs import ini, Pager, getsession, getch, Ansi, Msg
    import x84.default.writemsg
    session, term = getsession(), getterminal()

    session.activity = 'reading msgs'
    # build header
    #len_idx = max([len('%d' % (_idx,)) for _idx in msgs])
    len_idx = 40
    len_author = ini.CFG.getint('nua', 'max_user')
    len_ago = 9
    len_subject = ini.CFG.getint('msg', 'max_subject')
    len_preview = min(len_idx + len_author + len_ago + len_subject + -1, term.width - 2)
    reply_depth = ini.CFG.getint('msg', 'max_depth')
    indent_start, indent, indent_end = u'\\', u'-', u'> '

    def get_header(msgs_idx):
        """
        Return list of tuples, (idx, unicodestring), suitable for Lightbar.
        """
        import datetime
        msg_list = list()

        def head(msg, depth=0, maxdepth=reply_depth):
            """ This recursive routine finds the 'head' message
                of any relationship, up to maxdepth.
            """
            if (depth <= maxdepth
                    and hasattr(msg, 'parent')
                    and msg.parent is not None):
                return head(get_msg(msg.parent), depth + 1, maxdepth)
            return msg.idx, depth

        for idx, txt in enumerate(msgs_idx):
            author, subj = txt[0], txt[1]
            msg_list.append([idx, author, subj])
            
        return msg_list

    def get_selector(mailbox, prev_sel=None):
        """
        Provide Lightbar UI element given message mailbox returned from
        function get_header, and prev_sel as previously instantiated Lightbar.
        """
        from x84.bbs import Lightbar
        pos = prev_sel.position if prev_sel is not None else (0, 0)
        sel = Lightbar(
            height=(term.height / 3
                    if term.width < 140 else term.height - 3),
            width=len_preview, yloc=2, xloc=0)
        sel.glyphs['top-horiz'] = u''
        sel.glyphs['left-vert'] = u''
        sel.colors['highlight'] = term.yellow_reverse
        sel.update(mailbox)
        sel.position = pos
        return sel

    def get_reader():
        """
        Provide Pager UI element for message reading.
        """
        reader_height = (term.height - (term.height / 3) - 2)
        reader_indent = 2
        reader_width = min(term.width - 1, min(term.width - reader_indent, 80))
        reader_ypos = ((term.height - 1 ) - reader_height if
                      (term.width - reader_width) < len_preview else 2)
        reader_height = term.height - reader_ypos - 1
        msg_reader = Pager(
            height=reader_height,
            width=reader_width,
            yloc=reader_ypos,
            xloc=min(len_preview + 2, term.width - reader_width))
        msg_reader.glyphs['top-horiz'] = u''
        msg_reader.glyphs['right-vert'] = u''
        return msg_reader

#   def format_msg(reader, idx):
#       """ Format message of index ``idx`` into Pager instance ``reader``. """
#       msg = msgs[idx]
#       author = msg[1][0]
#       body = msg[1][1]
#       ucs = u'\r\n'.join((
#           (u''.join((
#               term.yellow('fROM: '),
#               (u'%s' % term.bold(author,)).rjust(len(author)),
#               u' ' * (reader.visible_width - (len(author) )),
#               ))),
#           u''.join((
#               term.yellow('tO: '),
#           (Ansi(
#               term.yellow('tAGS: ')
#               + (u'%s ' % (term.bold(','),)).join((
#                   [term.bold_red(_tag)
#                       if _tag in SEARCH_TAGS
#                       else term.yellow(_tag)
#                       for _tag in msg.tags]))).wrap(
#                           reader.visible_width,
#                           indent=u'      ')),
#           (term.yellow_underline(
#               (u'SUbj: %s' % (msg.subject,)).ljust(reader.visible_width)
#           )),
#           u'', (msg.body),))
#       return ucs

    def get_selector_title(mbox):
        return cachetime

    def get_selector_footer(currentpage, totalpages):
        return 'Page '+currentpage+'/'+totalpages 

    def get_reader_footer(idx):
        """
        Returns unicode string suitable for displaying
        as footer of reader when window is active
        """

        return u''.join((
            idx,
            term.yellow(u'- '),
            u' '.join((
                term.yellow_underline(u'<') + u':back ',
                term.yellow_underline(u'r') + u':eply ',
                term.yellow_underline(u'q') + u':uit',)),
            term.yellow(u' -'),))

    def refresh(reader, selector, mbox, title):
        """
        Returns unicode string suitable for refreshing the screen.
        """
        if READING:
            reader.colors['border'] = term.bold_yellow
            selector.colors['border'] = term.bold_black
        else:
            reader.colors['border'] = term.bold_black
            selector.colors['border'] = term.bold_yellow
        padd_attr = (term.bold_yellow if not READING
                     else term.bold_black)
        sel_padd_right = padd_attr(
            u'-'
            + selector.glyphs['bot-horiz'] * (
            selector.visible_width - len(Ansi(str(title))) - 7)
            + u'-\u25a0-' if READING else u'- -')
        sel_padd_left = padd_attr(
            selector.glyphs['bot-horiz'] * 3)
        idx = selector.selection[0]
        return u''.join((term.move(0, 0), term.clear, u'\r\n',cachetime,
                         u'// REAdiNG MSGS ..'.center(term.width).rstrip(),
                         selector.refresh(),
                         selector.border() if READING else reader.border(),
                         reader.border() if READING else selector.border(),
                         selector.title(
                             sel_padd_left + title + sel_padd_right),
                         selector.footer(get_selector_footer(currentpage, totalpages)
                                         ) if not READING else u'',
                         #reader.footer(get_reader_footer(u'Post '+str(idx))
                         reader.footer(get_reader_footer(cachetime)
                                       ) if READING else u'',
                         reader.refresh(),
                         ))

    echo((u'\r\n' + term.clear_eol) * (term.height - 1))
    dirty = 2
    msg_selector = None
    msg_reader = None
    idx = None
    # pylint: disable=W0603
    #         Using the global statement
    global READING
    while (msg_selector is None and msg_reader is None
           ) or not (msg_selector.quit or msg_reader.quit):
        if session.poll_event('refresh'):
            dirty = 2
        if dirty:
            if dirty == 2:
                mailbox = get_header(msgs)
            msg_selector = get_selector(mailbox, msg_selector)
            idx = msg_selector.selection[0]
            msg_reader = get_reader()
            msg_reader.update(msgs[idx][1])
            echo(refresh(msg_reader, msg_selector, msgs, title))
            dirty = 0
        inp = getch(1)
        if inp in (u'r', u'R'):
            reply_msgbody = quote_body(msgs[idx][1],
                                        max(30, min(79, term.width - 4)), msgs[idx][0])
            echo(term.move(term.height, 0) + u'\r\n')
            session.user['draft'] = reply_msgbody
            if gosub('editor', 'draft'):
                makepost(threadid, session.user['draft'])
                dirty = 2
                READING = False
            else:
                dirty = 1
            #mark_read(idx)  # also mark as read

        # 't' uses writemsg.prompt_tags() routine, how confusing ..
        elif inp in (u't',) and allow_tag(idx):
            echo(term.move(term.height, 0))
            msg = get_msg(idx)
            if x84.default.writemsg.prompt_tags(msg):
                msg.save()
            dirty = 2

        # spacebar marks as read, goes to next message
        elif inp in (u' ',):
            dirty = 1#2 if mark_read(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # D marks as deleted, goes to next message
        elif inp in (u'D',):
            dirty = 2 if mark_delete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # U undeletes, does not move.
        elif inp in (u'U',):
            dirty = 2 if mark_undelete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        if READING:
            echo(msg_reader.process_keystroke(inp))
            # left, <, or backspace moves UI
            if inp in (term.KEY_LEFT, u'<', u'h',
                       '\b', term.KEY_BACKSPACE):
                READING = False
                dirty = 1
        else:
            echo(msg_selector.process_keystroke(inp))
            idx = msg_selector.selection[0]
            # right, >, or enter marks message read, moves UI
            if inp in (u'\r', term.KEY_ENTER, u'>',
                       u'l', 'L', term.KEY_RIGHT):
                dirty = 1#2 if mark_read(idx) else 1
                READING = True
            elif msg_selector.moved:
                dirty = 1
    echo(term.move(term.height, 0) + u'\r\n')
    return
Пример #16
0
def msg_filter(msgs):
    """
        filter all matching messages. userland implementation
        of private/public messaging by using the 'tags' database.
        'new', or unread messages are marked by idx matching
        session.user['readmsgs'] when read. Finally, implement 'group'
        tagging, so that users of group 'impure' are allowed to read
        messages tagged by 'impure', regardless of recipient or 'public'.

        returns (msgs), (new). new contains redundant msgs
    """
    # pylint: disable=R0914,R0912,R0915
    #         Too many local variables
    #         Too many branches
    #         Too many statements
    from x84.bbs import list_msgs, echo, getsession, getterminal, get_msg, Ansi
    session, term = getsession(), getterminal()
    public_msgs = list_msgs(('public',))
    addressed_to = 0
    addressed_grp = 0
    filtered = 0
    deleted = 0
    private = 0
    public = 0
    new = set()
    echo(u' Processing ' + term.reverse_yellow('..'))
    for msg_id in msgs.copy():
        if msg_id in public_msgs:
            # can always ready msgs tagged with 'public'
            public += 1
        else:
            private += 1
        msg = get_msg(msg_id)
        if msg.recipient == session.user.handle:
            addressed_to += 1
        else:
            # a system of my own, by creating groups
            # with the same as tagged messages, you may
            # create private or intra-group messages.
            tag_matches_group = False
            for tag in msg.tags:
                if tag in session.user.groups:
                    tag_matches_group = True
                    break
            if tag_matches_group:
                addressed_grp += 1
            elif msg_id not in public_msgs:
                # denied to read this message
                if FILTER_PRIVATE:
                    msgs.remove(msg_id)
                    filtered += 1
                    continue
            elif msg_id in DELETED:
                msgs.remove(msg_id)
                deleted += 1
        if msg_id not in ALREADY_READ:
            new.add(msg_id)

    if 0 == len(msgs):
        echo(u'\r\n\r\nNo messages (%s filtered).' % (filtered,))
    else:
        txt_out = list()
        if addressed_to > 0:
            txt_out.append('%s addressed to you' % (
                term.bold_yellow(str(addressed_to)),))
        if addressed_grp > 0:
            txt_out.append('%s addressed by group' % (
                term.bold_yellow(str(addressed_grp)),))
        if filtered > 0:
            txt_out.append('%s filtered' % (
                term.bold_yellow(str(filtered)),))
        if deleted > 0:
            txt_out.append('%s deleted' % (
                term.bold_yellow(str(deleted)),))
        if public > 0:
            txt_out.append('%s public' % (
                term.bold_yellow(str(public)),))
        if private > 0:
            txt_out.append('%s private' % (
                term.bold_yellow(str(private)),))
        if len(new) > 0:
            txt_out.append('%s new' % (
                term.bold_yellow(str(len(new),)),))
        if 0 != len(txt_out):
            echo(u'\r\n\r\n' + Ansi(
                u', '.join(txt_out) + u'.').wrap(term.width - 2))
    return msgs, new
Пример #17
0
def do_reader_prompt(session, term, index, message_indices, colors):
    xpos = max(0, (term.width // 2) - (80 // 2))
    opts = []
    if index:
        opts += (('p', 'rev'),)
    if index < len(message_indices) - 1:
        opts += (('n', 'ext'),)
    if allow_tag(session, message_indices[index]):
        opts += (('e', 'dit tags'),)
    if can_delete(session):
        opts += (('D', 'elete'),)
    opts += (('r', 'eply'),)
    opts += (('q', 'uit'),)
    opts += (('idx', ''),)
    while True:
        echo(term.move_x(xpos))
        echo(u''.join((
            colors['lowlight'](u'['),
            colors['highlight'](str(index + 1)),
            u'/{0}'.format(len(message_indices)),
            colors['lowlight'](u']'),
            u' ',
            u', '.join((
                u''.join((colors['lowlight'](u'['),
                          colors['highlight'](key),
                          colors['lowlight'](u']'),
                          value
                          )) for key, value in opts)),
            u': ',
            term.clear_eol,
        )))
        width = max(2, len(str(len(message_indices))))
        inp = LineEditor(width, colors={'highlight': colors['backlight']}).read()
        if inp is None or inp.lower() == u'q':
            return None
        elif inp in (u'n', u''):
            # 'n'ext or return key
            echo(term.move_x(xpos) + term.clear_eol)
            if index == len(message_indices) - 1:
                # no more messages,
                return None
            return index + 1
        elif inp == u'p' and index > 0:
            # prev
            echo(term.move_x(xpos) + term.clear_eol)
            return index - 1
        elif inp == u'e' and allow_tag(session, message_indices[index]):
            msg = get_msg(message_indices[index])
            echo(u'\r\n')
            if prompt_tags(session=session, term=term, msg=msg,
                           colors=colors, public='public' in msg.tags):
                echo(u'\r\n')
                msg.save()
            return index
        elif inp == u'D' and can_delete(session):
            delete_message(msg=get_msg(message_indices[index]))
            return None
        elif inp == u'r':
            # write message reply
            msg = create_reply_message(session=session,
                                       idx=message_indices[index])
            if (
                    prompt_subject(
                        term=term, msg=msg, colors=colors
                    ) and prompt_body(
                        term=term, msg=msg, colors=colors
                    ) and prompt_tags(
                        session=session, term=term, msg=msg,
                        colors=colors, public='public' in msg.tags
                    )):
                do_send_message(session=session, term=term,
                                msg=msg, colors=colors)
            break
        else:
            # not a valid input option, is it a valid integer? (even '-1'!)
            try:
                val = int(inp)
            except ValueError:
                # some garbage; try again
                term.inkey(0.15)
                continue
            try:
                # allow a message index, (even pythonic '-1' for 'last')
                if val > 0:
                    # 1-based indexing
                    val -= 1
                nxt_idx = message_indices.index(message_indices[val])
                if nxt_idx != index:
                    echo(term.move_x(xpos) + term.clear_eol)
                    return nxt_idx
            except (IndexError, ValueError):
                # invalid index; try again
                term.inkey(0.15)
                continue
Пример #18
0
def read_messages(msgs, new):
    """
    Provide message reader UI given message list ``msgs``,
    with new messages in list ``new``.
    """
    # pylint: disable=R0914,R0912,R0915
    #         Too many local variables
    #         Too many branches
    #         Too many statements
    from x84.bbs import timeago, get_msg, getterminal, echo, gosub
    from x84.bbs import ini, Pager, getsession, getch, Ansi, Msg
    import x84.default.writemsg
    session, term = getsession(), getterminal()

    session.activity = 'reading msgs'
    # build header
    len_idx = max([len('%d' % (_idx,)) for _idx in msgs])
    len_author = ini.CFG.getint('nua', 'max_user')
    len_ago = 9
    len_subject = ini.CFG.getint('msg', 'max_subject')
    len_preview = min(len_idx + len_author + len_ago + len_subject + -1, term.width - 2)
    reply_depth = ini.CFG.getint('msg', 'max_depth')
    indent_start, indent, indent_end = u'\\', u'-', u'> '

    def get_header(msgs_idx):
        """
        Return list of tuples, (idx, unicodestring), suitable for Lightbar.
        """
        import datetime
        msg_list = list()
        thread_indent = lambda depth: (term.red(
            (indent_start + (indent * depth) + indent_end))
            if depth else u'')

        def head(msg, depth=0, maxdepth=reply_depth):
            """ This recursive routine finds the 'head' message
                of any relationship, up to maxdepth.
            """
            if (depth <= maxdepth
                    and hasattr(msg, 'parent')
                    and msg.parent is not None):
                return head(get_msg(msg.parent), depth + 1, maxdepth)
            return msg.idx, depth

        for idx in msgs_idx:
            msg = get_msg(idx)
            author, subj = msg.author, msg.subject
            tm_ago = (datetime.datetime.now() - msg.stime).total_seconds()
            # pylint: disable=W0631
            #         Using possibly undefined loop variable 'idx'
            attr = lambda arg: (
                term.bold_green(arg) if (
                    not idx in ALREADY_READ
                    and msg.recipient == session.user.handle) else
                term.red(arg) if not idx in ALREADY_READ
                else term.yellow(arg))
            status = [u'U' if not idx in ALREADY_READ else u' ',
                      u'D' if idx in DELETED else u' ', ]
            row_txt = u'%s %s %s %s %s%s ' % (
                u''.join(status),
                attr(str(idx).rjust(len_idx)),
                attr(author.ljust(len_author)),
                (timeago(tm_ago)).rjust(len_ago),
                attr(u'ago'),
                term.bold_black(':'),)
            msg_list.append((head(msg), idx, row_txt, subj))
        msg_list.sort()
        return [(idx, row_txt + thread_indent(depth) + subj)
                for (_threadid, depth), idx, row_txt, subj in msg_list]

    def get_selector(mailbox, prev_sel=None):
        """
        Provide Lightbar UI element given message mailbox returned from
        function get_header, and prev_sel as previously instantiated Lightbar.
        """
        from x84.bbs import Lightbar
        pos = prev_sel.position if prev_sel is not None else (0, 0)
        sel = Lightbar(
            height=(term.height / 3
                    if term.width < 140 else term.height - 3),
            width=len_preview,
            yloc=2, xloc=0)
        sel.glyphs['top-horiz'] = u''
        sel.glyphs['left-vert'] = u''
        sel.colors['highlight'] = term.yellow_reverse
        sel.update(mailbox)
        sel.position = pos
        return sel

    def get_reader():
        """
        Provide Pager UI element for message reading.
        """
        reader_height = (term.height - (term.height / 3) - 2)
        reader_indent = 2
        reader_width = min(term.width - 1, min(term.width - reader_indent, 80))
        reader_ypos = ((term.height - 1 ) - reader_height if
                      (term.width - reader_width) < len_preview else 2)
        reader_height = term.height - reader_ypos - 1
        msg_reader = Pager(
            height=reader_height,
            width=reader_width,
            yloc=reader_ypos,
            xloc=min(len_preview + 2, term.width - reader_width))
        msg_reader.glyphs['top-horiz'] = u''
        msg_reader.glyphs['right-vert'] = u''
        return msg_reader

    def format_msg(reader, idx):
        """ Format message of index ``idx`` into Pager instance ``reader``. """
        msg = get_msg(idx)
        sent = msg.stime.strftime(TIME_FMT)
        to_attr = term.bold_green if (
            msg.recipient == session.user.handle) else term.underline
        ucs = u'\r\n'.join((
            (u''.join((
                term.yellow('fROM: '),
                (u'%s' % term.bold(msg.author,)).rjust(len_author),
                u' ' * (reader.visible_width - (len_author + len(sent))),
                sent,))),
            u''.join((
                term.yellow('tO: '),
                to_attr((u'%s' % to_attr(msg.recipient,)).rjust(len_author)
                        if msg.recipient is not None else u'All'),)),
            (Ansi(
                term.yellow('tAGS: ')
                + (u'%s ' % (term.bold(','),)).join((
                    [term.bold_red(_tag)
                        if _tag in SEARCH_TAGS
                        else term.yellow(_tag)
                        for _tag in msg.tags]))).wrap(
                            reader.visible_width,
                            indent=u'      ')),
            (term.yellow_underline(
                (u'SUbj: %s' % (msg.subject,)).ljust(reader.visible_width)
            )),
            u'', (msg.body),))
        return ucs

    def get_selector_title(mbox, new):
        """
        Returns unicode string suitable for displaying as title of mailbox.
        """
        newmsg = (term.yellow(u' ]-[ ') +
                  term.yellow_reverse(str(len(new))) +
                  term.bold_underline(u' NEW')) if len(new) else u''
        return u''.join((term.yellow(u'[ '),
                         term.bold_yellow(str(len(mbox))),
                         term.bold(
                         u' MSG%s' % (u's' if 1 != len(mbox) else u'',)),
                         newmsg, term.yellow(u' ]'),))

    dispcmd_mark = lambda idx: (
        (term.yellow_underline(u' ') + u':mark' + u' ')
        if idx not in ALREADY_READ else u'')
    dispcmd_delete = lambda idx: (
        (term.yellow_underline(u'D') + u':elete' + u' ')
        if idx not in DELETED else u'')
    dispcmd_tag = lambda idx: (
        (term.yellow_underline(u't') + u':ag' + u' ')
        if allow_tag(idx) else u'')

    def get_selector_footer(idx):
        """
        Returns unicode string suitable for displaying
        as footer of mailbox when window is active.
        """
        return u''.join((
            term.yellow(u'- '),
            u''.join((
                term.yellow_underline(u'>') + u':read ',
                term.yellow_underline(u'r') + u':eply ',
                dispcmd_mark(idx),
                dispcmd_delete(idx),
                dispcmd_tag(idx),
                term.yellow_underline(u'q') + u':uit',)),
            term.yellow(u' -'),))

    def get_reader_footer(idx):
        """
        Returns unicode string suitable for displaying
        as footer of reader when window is active
        """

        return u''.join((
            term.yellow(u'- '),
            u' '.join((
                term.yellow_underline(u'<') + u':back ',
                term.yellow_underline(u'r') + u':eply ',
                dispcmd_delete(idx),
                dispcmd_tag(idx),
                term.yellow_underline(u'q') + u':uit',)),
            term.yellow(u' -'),))

    def refresh(reader, selector, mbox, new):
        """
        Returns unicode string suitable for refreshing the screen.
        """
        if READING:
            reader.colors['border'] = term.bold_yellow
            selector.colors['border'] = term.bold_black
        else:
            reader.colors['border'] = term.bold_black
            selector.colors['border'] = term.bold_yellow
        title = get_selector_title(mbox, new)
        padd_attr = (term.bold_yellow if not READING
                     else term.bold_black)
        sel_padd_right = padd_attr(
            u'-'
            + selector.glyphs['bot-horiz'] * (
            selector.visible_width - len(Ansi(title)) - 7)
            + u'-\u25a0-' if READING else u'- -')
        sel_padd_left = padd_attr(
            selector.glyphs['bot-horiz'] * 3)
        idx = selector.selection[0]
        return u''.join((term.move(0, 0), term.clear, u'\r\n',
                         u'// REAdiNG MSGS ..'.center(term.width).rstrip(),
                         selector.refresh(),
                         selector.border() if READING else reader.border(),
                         reader.border() if READING else selector.border(),
                         selector.title(
                             sel_padd_left + title + sel_padd_right),
                         selector.footer(get_selector_footer(idx)
                                         ) if not READING else u'',
                         reader.footer(get_reader_footer(idx)
                                       ) if READING else u'',
                         reader.refresh(),
                         ))

    echo((u'\r\n' + term.clear_eol) * (term.height - 1))
    dirty = 2
    msg_selector = None
    msg_reader = None
    idx = None
    # pylint: disable=W0603
    #         Using the global statement
    global READING
    while (msg_selector is None and msg_reader is None
           ) or not (msg_selector.quit or msg_reader.quit):
        if session.poll_event('refresh'):
            dirty = 2
        if dirty:
            if dirty == 2:
                mailbox = get_header(msgs)
            msg_selector = get_selector(mailbox, msg_selector)
            idx = msg_selector.selection[0]
            msg_reader = get_reader()
            msg_reader.update(format_msg(msg_reader, idx))
            echo(refresh(msg_reader, msg_selector, msgs, new))
            dirty = 0
        inp = getch(1)
        if inp in (u'r', u'R'):
            reply_to = get_msg(idx)
            reply_msg = Msg()
            reply_msg.recipient = reply_to.author
            reply_msg.tags = reply_to.tags
            reply_msg.subject = reply_to.subject
            reply_msg.parent = reply_to.idx
            # quote between 30 and 79, 'screen width - 4' as variable dist.
            reply_msg.body = quote_body(reply_to,
                                        max(30, min(79, term.width - 4)))
            echo(term.move(term.height, 0) + u'\r\n')
            if gosub('writemsg', reply_msg):
                reply_msg.save()
                dirty = 2
                READING = False
            else:
                dirty = 1
            mark_read(idx)  # also mark as read

        # 't' uses writemsg.prompt_tags() routine, how confusing ..
        elif inp in (u't',) and allow_tag(idx):
            echo(term.move(term.height, 0))
            msg = get_msg(idx)
            if x84.default.writemsg.prompt_tags(msg):
                msg.save()
            dirty = 2

        # spacebar marks as read, goes to next message
        elif inp in (u' ',):
            dirty = 2 if mark_read(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # D marks as deleted, goes to next message
        elif inp in (u'D',):
            dirty = 2 if mark_delete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # U undeletes, does not move.
        elif inp in (u'U',):
            dirty = 2 if mark_undelete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        if READING:
            echo(msg_reader.process_keystroke(inp))
            # left, <, or backspace moves UI
            if inp in (term.KEY_LEFT, u'<', u'h',
                       '\b', term.KEY_BACKSPACE):
                READING = False
                dirty = 1
        else:
            echo(msg_selector.process_keystroke(inp))
            idx = msg_selector.selection[0]
            # right, >, or enter marks message read, moves UI
            if inp in (u'\r', term.KEY_ENTER, u'>',
                       u'l', 'L', term.KEY_RIGHT):
                dirty = 2 if mark_read(idx) else 1
                READING = True
            elif msg_selector.moved:
                dirty = 1
    echo(term.move(term.height, 0) + u'\r\n')
    return
Пример #19
0
def do_reader_prompt(session, term, index, message_indices, colors):
    xpos = max(0, (term.width // 2) - (80 // 2))
    opts = []
    if index:
        opts += (('p', 'rev'), )
    if index < len(message_indices) - 1:
        opts += (('n', 'ext'), )
    if allow_tag(session, message_indices[index]):
        opts += (('e', 'dit tags'), )
    if can_delete(session):
        opts += (('D', 'elete'), )
    opts += (('r', 'eply'), )
    opts += (('q', 'uit'), )
    opts += (('idx', ''), )
    while True:
        echo(term.move_x(xpos))
        echo(u''.join((
            colors['lowlight'](u'['),
            colors['highlight'](str(index + 1)),
            u'/{0}'.format(len(message_indices)),
            colors['lowlight'](u']'),
            u' ',
            u', '.join((u''.join(
                (colors['lowlight'](u'['), colors['highlight'](key),
                 colors['lowlight'](u']'), value)) for key, value in opts)),
            u': ',
            term.clear_eol,
        )))
        width = max(2, len(str(len(message_indices))))
        inp = LineEditor(width, colors={
            'highlight': colors['backlight']
        }).read()
        if inp is None or inp.lower() == u'q':
            return None
        elif inp in (u'n', u''):
            # 'n'ext or return key
            echo(term.move_x(xpos) + term.clear_eol)
            if index == len(message_indices) - 1:
                # no more messages,
                return None
            return index + 1
        elif inp == u'p' and index > 0:
            # prev
            echo(term.move_x(xpos) + term.clear_eol)
            return index - 1
        elif inp == u'e' and allow_tag(session, message_indices[index]):
            msg = get_msg(message_indices[index])
            echo(u'\r\n')
            if prompt_tags(session=session,
                           term=term,
                           msg=msg,
                           colors=colors,
                           public='public' in msg.tags):
                echo(u'\r\n')
                msg.save()
            return index
        elif inp == u'D' and can_delete(session):
            delete_message(msg=get_msg(message_indices[index]))
            return None
        elif inp == u'r':
            # write message reply
            msg = create_reply_message(session=session,
                                       idx=message_indices[index])
            if (prompt_subject(term=term, msg=msg, colors=colors)
                    and prompt_body(term=term, msg=msg, colors=colors)
                    and prompt_tags(session=session,
                                    term=term,
                                    msg=msg,
                                    colors=colors,
                                    public='public' in msg.tags)):
                do_send_message(session=session,
                                term=term,
                                msg=msg,
                                colors=colors)
            break
        else:
            # not a valid input option, is it a valid integer? (even '-1'!)
            try:
                val = int(inp)
            except ValueError:
                # some garbage; try again
                term.inkey(0.15)
                continue
            try:
                # allow a message index, (even pythonic '-1' for 'last')
                if val > 0:
                    # 1-based indexing
                    val -= 1
                nxt_idx = message_indices.index(message_indices[val])
                if nxt_idx != index:
                    echo(term.move_x(xpos) + term.clear_eol)
                    return nxt_idx
            except (IndexError, ValueError):
                # invalid index; try again
                term.inkey(0.15)
                continue
Пример #20
0
def do_reader_prompt(session, term, index, message_indices, colors):
    xpos = max(0, (term.width // 2) - (80 // 2))
    opts = []
    if index:
        opts += (("p", "rev"),)
    if index < len(message_indices) - 1:
        opts += (("n", "ext"),)
    if allow_tag(session, message_indices[index]):
        opts += (("e", "dit tags"),)
    if can_delete(session):
        opts += (("D", "elete"),)
    opts += (("r", "eply"),)
    opts += (("q", "uit"),)
    opts += (("idx", ""),)
    while True:
        echo(term.move_x(xpos))
        echo(
            u"".join(
                (
                    colors["lowlight"](u"["),
                    colors["highlight"](str(index + 1)),
                    u"/{0}".format(len(message_indices)),
                    colors["lowlight"](u"]"),
                    u" ",
                    u", ".join(
                        (
                            u"".join(
                                (colors["lowlight"](u"["), colors["highlight"](key), colors["lowlight"](u"]"), value)
                            )
                            for key, value in opts
                        )
                    ),
                    u": ",
                    term.clear_eol,
                )
            )
        )
        width = max(2, len(str(len(message_indices))))
        inp = LineEditor(width, colors={"highlight": colors["backlight"]}).read()
        if inp is None or inp.lower() == u"q":
            return None
        elif inp in (u"n", u""):
            # 'n'ext or return key
            echo(term.move_x(xpos) + term.clear_eol)
            if index == len(message_indices) - 1:
                # no more messages,
                return None
            return index + 1
        elif inp == u"p" and index > 0:
            # prev
            echo(term.move_x(xpos) + term.clear_eol)
            return index - 1
        elif inp == u"e" and allow_tag(session, message_indices[index]):
            msg = get_msg(message_indices[index])
            echo(u"\r\n")
            if prompt_tags(session=session, term=term, msg=msg, colors=colors, public="public" in msg.tags):
                echo(u"\r\n")
                msg.save()
            return index
        elif inp == u"D" and can_delete(session):
            delete_message(msg=get_msg(message_indices[index]))
            return None
        elif inp == u"r":
            # write message reply
            msg = create_reply_message(session=session, idx=message_indices[index])
            if (
                prompt_subject(term=term, msg=msg, colors=colors)
                and prompt_body(term=term, msg=msg, colors=colors)
                and prompt_tags(session=session, term=term, msg=msg, colors=colors, public="public" in msg.tags)
            ):
                do_send_message(session=session, term=term, msg=msg, colors=colors)
            break
        else:
            # not a valid input option, is it a valid integer? (even '-1'!)
            try:
                val = int(inp)
            except ValueError:
                # some garbage; try again
                term.inkey(0.15)
                continue
            try:
                # allow a message index, (even pythonic '-1' for 'last')
                if val > 0:
                    # 1-based indexing
                    val -= 1
                nxt_idx = message_indices.index(message_indices[val])
                if nxt_idx != index:
                    echo(term.move_x(xpos) + term.clear_eol)
                    return nxt_idx
            except (IndexError, ValueError):
                # invalid index; try again
                term.inkey(0.15)
                continue
Пример #21
0
def msg_filter(msgs):
    """
        filter all matching messages. userland implementation
        of private/public messaging by using the 'tags' database.
        'new', or unread messages are marked by idx matching
        session.user['readmsgs'] when read. Finally, implement 'group'
        tagging, so that users of group 'impure' are allowed to read
        messages tagged by 'impure', regardless of recipient or 'public'.

        returns (msgs), (new). new contains redundant msgs
    """
    # pylint: disable=R0914,R0912,R0915
    #         Too many local variables
    #         Too many branches
    #         Too many statements
    from x84.bbs import list_msgs, echo, getsession, getterminal, get_msg
    session, term = getsession(), getterminal()
    public_msgs = list_msgs(('public', ))
    addressed_to = 0
    addressed_grp = 0
    filtered = 0
    deleted = 0
    private = 0
    public = 0
    new = set()
    echo(u' Processing ' + term.reverse_yellow('..'))
    for msg_id in msgs.copy():
        if msg_id in public_msgs:
            # can always ready msgs tagged with 'public'
            public += 1
        else:
            private += 1
        msg = get_msg(msg_id)
        if msg.recipient == session.user.handle:
            addressed_to += 1
        else:
            # a system of my own, by creating groups
            # with the same as tagged messages, you may
            # create private or intra-group messages.
            tag_matches_group = False
            for tag in msg.tags:
                if tag in session.user.groups:
                    tag_matches_group = True
                    break
            if tag_matches_group:
                addressed_grp += 1
            elif msg_id not in public_msgs:
                # denied to read this message
                if FILTER_PRIVATE:
                    msgs.remove(msg_id)
                    filtered += 1
                    continue
            elif msg_id in DELETED:
                msgs.remove(msg_id)
                deleted += 1
        if msg_id not in ALREADY_READ:
            new.add(msg_id)

    if 0 == len(msgs):
        echo(u'\r\n\r\nNo messages (%s filtered).' % (filtered, ))
    else:
        txt_out = list()
        if addressed_to > 0:
            txt_out.append('%s addressed to you' %
                           (term.bold_yellow(str(addressed_to)), ))
        if addressed_grp > 0:
            txt_out.append('%s addressed by group' %
                           (term.bold_yellow(str(addressed_grp)), ))
        if filtered > 0:
            txt_out.append('%s filtered' % (term.bold_yellow(str(filtered)), ))
        if deleted > 0:
            txt_out.append('%s deleted' % (term.bold_yellow(str(deleted)), ))
        if public > 0:
            txt_out.append('%s public' % (term.bold_yellow(str(public)), ))
        if private > 0:
            txt_out.append('%s private' % (term.bold_yellow(str(private)), ))
        if len(new) > 0:
            txt_out.append('%s new' % (term.bold_yellow(str(len(new), )), ))
        if 0 != len(txt_out):
            echo(u'\r\n\r\n' + u'\r\n'.join(
                term.wrap(u', '.join(txt_out) + u'.', (term.width - 2))))
    return msgs, new
Пример #22
0
def read_messages(msgs, new):
    """
    Provide message reader UI given message list ``msgs``,
    with new messages in list ``new``.
    """
    # pylint: disable=R0914,R0912,R0915
    #         Too many local variables
    #         Too many branches
    #         Too many statements
    from x84.bbs import timeago, get_msg, getterminal, echo, gosub
    from x84.bbs import ini, Pager, getsession, getch, Msg
    import x84.default.writemsg
    session, term = getsession(), getterminal()

    session.activity = 'reading msgs'
    # build header
    len_idx = max([len('%d' % (_idx, )) for _idx in msgs])
    len_author = ini.CFG.getint('nua', 'max_user')
    len_ago = 9
    len_subject = ini.CFG.getint('msg', 'max_subject')
    len_preview = min(len_idx + len_author + len_ago + len_subject + -1,
                      term.width - 2)
    reply_depth = ini.CFG.getint('msg', 'max_depth')
    indent_start, indent, indent_end = u'\\', u'-', u'> '

    def get_header(msgs_idx):
        """
        Return list of tuples, (idx, unicodestring), suitable for Lightbar.
        """
        import datetime
        msg_list = list()
        thread_indent = lambda depth: (term.red(
            (indent_start + (indent * depth) + indent_end)) if depth else u'')

        def head(msg, depth=0, maxdepth=reply_depth):
            """ This recursive routine finds the 'head' message
                of any relationship, up to maxdepth.
            """
            if (depth <= maxdepth and hasattr(msg, 'parent')
                    and msg.parent is not None):
                return head(get_msg(msg.parent), depth + 1, maxdepth)
            return msg.idx, depth

        for idx in msgs_idx:
            msg = get_msg(idx)
            author, subj = msg.author, msg.subject
            tm_ago = (datetime.datetime.now() - msg.stime).total_seconds()
            # pylint: disable=W0631
            #         Using possibly undefined loop variable 'idx'
            attr = lambda arg: (term.bold_green(arg)
                                if (not idx in ALREADY_READ and msg.recipient
                                    == session.user.handle) else term.red(arg)
                                if not idx in ALREADY_READ else term.yellow(arg
                                                                            ))
            status = [
                u'U' if not idx in ALREADY_READ else u' ',
                u'D' if idx in DELETED else u' ',
            ]
            row_txt = u'%s %s %s %s %s%s ' % (
                u''.join(status),
                attr(str(idx).rjust(len_idx)),
                attr(author.ljust(len_author)),
                (timeago(tm_ago)).rjust(len_ago),
                attr(u'ago'),
                term.bold_black(':'),
            )
            msg_list.append((head(msg), idx, row_txt, subj))
        msg_list.sort(reverse=True)
        return [(idx, row_txt + thread_indent(depth) + subj)
                for (_threadid, depth), idx, row_txt, subj in msg_list]

    def get_selector(mailbox, prev_sel=None):
        """
        Provide Lightbar UI element given message mailbox returned from
        function get_header, and prev_sel as previously instantiated Lightbar.
        """
        from x84.bbs import Lightbar
        pos = prev_sel.position if prev_sel is not None else (0, 0)
        sel = Lightbar(height=(term.height /
                               3 if term.width < 140 else term.height - 3),
                       width=len_preview,
                       yloc=2,
                       xloc=0)
        sel.glyphs['top-horiz'] = u''
        sel.glyphs['left-vert'] = u''
        sel.colors['highlight'] = term.yellow_reverse
        sel.update(mailbox)
        sel.position = pos
        return sel

    def get_reader():
        """
        Provide Pager UI element for message reading.
        """
        reader_height = (term.height - (term.height / 3) - 2)
        reader_indent = 2
        reader_width = min(term.width - 1, min(term.width - reader_indent, 80))
        reader_ypos = ((term.height - 1) - reader_height if
                       (term.width - reader_width) < len_preview else 2)
        reader_height = term.height - reader_ypos - 1
        msg_reader = Pager(height=reader_height,
                           width=reader_width,
                           yloc=reader_ypos,
                           xloc=min(len_preview + 2,
                                    term.width - reader_width))
        msg_reader.glyphs['top-horiz'] = u''
        msg_reader.glyphs['right-vert'] = u''
        return msg_reader

    def format_msg(reader, idx):
        """ Format message of index ``idx`` into Pager instance ``reader``. """
        msg = get_msg(idx)
        sent = msg.stime.strftime(TIME_FMT)
        to_attr = term.bold_green if (
            msg.recipient == session.user.handle) else term.underline
        ucs = u'\r\n'.join((
            (u''.join((
                term.yellow('fROM: '),
                (u'%s' % term.bold(msg.author, )).rjust(len_author),
                u' ' * (reader.visible_width - (len_author + len(sent))),
                sent,
            ))),
            u''.join((
                term.yellow('tO: '),
                to_attr((
                    u'%s' % to_attr(msg.recipient, )
                ).rjust(len_author) if msg.recipient is not None else u'All'),
            )),
            (u'\r\n'.join((term.wrap(
                term.yellow('tAGS: ') + (u'%s ' % (term.bold(','), )).join(([
                    term.bold_red(_tag)
                    if _tag in SEARCH_TAGS else term.yellow(_tag)
                    for _tag in msg.tags
                ])),
                reader.visible_width,
                subsequent_indent=u' ' * 6)))),
            (term.yellow_underline(
                (u'SUbj: %s' % (msg.subject, )).ljust(reader.visible_width))),
            u'',
            (msg.body),
        ))
        return ucs

    def get_selector_title(mbox, new):
        """
        Returns unicode string suitable for displaying as title of mailbox.
        """
        newmsg = (term.yellow(u' ]-[ ') + term.yellow_reverse(str(len(new))) +
                  term.bold_underline(u' NEW')) if len(new) else u''
        return u''.join((
            term.yellow(u'[ '),
            term.bold_yellow(str(len(mbox))),
            term.bold(u' MSG%s' % (u's' if 1 != len(mbox) else u'', )),
            newmsg,
            term.yellow(u' ]'),
        ))

    dispcmd_mark = lambda idx: ((term.yellow_underline(u' ') + u':mark' + u' ')
                                if idx not in ALREADY_READ else u'')
    dispcmd_delete = lambda idx: (
        (term.yellow_underline(u'D') + u':elete' + u' ')
        if idx not in DELETED else u'')
    dispcmd_tag = lambda idx: ((term.yellow_underline(u't') + u':ag' + u' ')
                               if allow_tag(idx) else u'')

    def get_selector_footer(idx):
        """
        Returns unicode string suitable for displaying
        as footer of mailbox when window is active.
        """
        return u''.join((
            term.yellow(u'- '),
            u''.join((
                term.yellow_underline(u'>') + u':read ',
                term.yellow_underline(u'r') + u':eply ',
                dispcmd_mark(idx),
                dispcmd_delete(idx),
                dispcmd_tag(idx),
                term.yellow_underline(u'q') + u':uit',
            )),
            term.yellow(u' -'),
        ))

    def get_reader_footer(idx):
        """
        Returns unicode string suitable for displaying
        as footer of reader when window is active
        """

        return u''.join((
            term.yellow(u'- '),
            u' '.join((
                term.yellow_underline(u'<') + u':back ',
                term.yellow_underline(u'r') + u':eply ',
                dispcmd_delete(idx),
                dispcmd_tag(idx),
                term.yellow_underline(u'q') + u':uit',
            )),
            term.yellow(u' -'),
        ))

    def refresh(reader, selector, mbox, new):
        """
        Returns unicode string suitable for refreshing the screen.
        """
        from x84.bbs import getsession
        session = getsession()

        if READING:
            reader.colors['border'] = term.bold_yellow
            selector.colors['border'] = term.bold_black
        else:
            reader.colors['border'] = term.bold_black
            selector.colors['border'] = term.bold_yellow
        title = get_selector_title(mbox, new)
        padd_attr = (term.bold_yellow if not READING else term.bold_black)
        sel_padd_right = padd_attr(
            u'-' + selector.glyphs['bot-horiz'] *
            (selector.visible_width - term.length(title) - 7) +
            u'-\u25a0-' if READING else u'- -')
        sel_padd_left = padd_attr(selector.glyphs['bot-horiz'] * 3)
        idx = selector.selection[0]
        return u''.join((
            term.move(0, 0),
            term.clear,
            u'\r\n',
            u'// REAdiNG MSGS ..'.center(term.width).rstrip(),
            selector.refresh(),
            selector.border() if READING else reader.border(),
            reader.border() if READING else selector.border(),
            selector.title(sel_padd_left + title + sel_padd_right),
            selector.footer(get_selector_footer(idx)) if not READING else u'',
            reader.footer(get_reader_footer(idx)) if READING else u'',
            reader.refresh(),
        ))

    echo((u'\r\n' + term.clear_eol) * (term.height - 1))
    dirty = 2
    msg_selector = None
    msg_reader = None
    idx = None
    # pylint: disable=W0603
    #         Using the global statement
    global READING
    while (msg_selector is None and
           msg_reader is None) or not (msg_selector.quit or msg_reader.quit):
        if session.poll_event('refresh'):
            dirty = 2
        if dirty:
            if dirty == 2:
                mailbox = get_header(msgs)
            msg_selector = get_selector(mailbox, msg_selector)
            idx = msg_selector.selection[0]
            msg_reader = get_reader()
            msg_reader.update(format_msg(msg_reader, idx))
            echo(refresh(msg_reader, msg_selector, msgs, new))
            dirty = 0
        inp = getch(1)
        if inp in (u'r', u'R'):
            reply_to = get_msg(idx)
            reply_msg = Msg()
            reply_msg.recipient = reply_to.author
            reply_msg.tags = reply_to.tags
            reply_msg.subject = reply_to.subject
            reply_msg.parent = reply_to.idx
            # quote between 30 and 79, 'screen width - 4' as variable dist.
            reply_msg.body = quote_body(reply_to,
                                        max(30, min(79, term.width - 4)))
            echo(term.move(term.height, 0) + u'\r\n')
            if gosub('writemsg', reply_msg):
                reply_msg.save()
                dirty = 2
                READING = False
            else:
                dirty = 1
            mark_read(idx)  # also mark as read

        # 't' uses writemsg.prompt_tags() routine, how confusing ..
        elif inp in (u't', ) and allow_tag(idx):
            echo(term.move(term.height, 0))
            msg = get_msg(idx)
            if x84.default.writemsg.prompt_tags(msg):
                msg.save()
                session.user['msgs_sent'] = session.user.get('msgs_sent',
                                                             0) + 1
            dirty = 2

        # spacebar marks as read, goes to next message
        elif inp in (u' ', ):
            dirty = 2 if mark_read(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # D marks as deleted, goes to next message
        elif inp in (u'D', ):
            dirty = 2 if mark_delete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        # U undeletes, does not move.
        elif inp in (u'U', ):
            dirty = 2 if mark_undelete(idx) else 1
            msg_selector.move_down()
            idx = msg_selector.selection[0]
            READING = False

        if READING:
            echo(msg_reader.process_keystroke(inp))
            # left, <, or backspace moves UI
            if inp in (term.KEY_LEFT, u'<', u'h', '\b', term.KEY_BACKSPACE):
                READING = False
                dirty = 1
        else:
            echo(msg_selector.process_keystroke(inp))
            idx = msg_selector.selection[0]
            # right, >, or enter marks message read, moves UI
            if inp in (u'\r', term.KEY_ENTER, u'>', u'l', 'L', term.KEY_RIGHT):
                dirty = 2 if mark_read(idx) else 1
                READING = True
            elif msg_selector.moved:
                dirty = 1
    echo(term.move(term.height, 0) + u'\r\n')
    return