예제 #1
0
파일: writemsg.py 프로젝트: quastdog/x84
def prompt_tags(msg):
    """ Prompt for and return tags wished for message. """
    # pylint: disable=R0914,W0603
    #         Too many local variables
    #         Using the global statement
    from x84.bbs import DBProxy, echo, getterminal, getsession
    from x84.bbs import Ansi, LineEditor
    session, term = getsession(), getterminal()
    tagdb = DBProxy('tags')
    msg_onlymods = (u"\r\nONlY MEMbERS Of thE '%s' OR '%s' "
                    "GROUP MAY CREAtE NEW tAGS." % (
                        term.bold_yellow('sysop'),
                        term.bold_blue('moderator'),))
    msg_invalidtag = u"\r\n'%s' is not a valid tag."
    prompt_tags1 = u"ENtER %s, COMMA-dEliMitEd. " % (
        term.bold_red('TAG(s)'),)
    prompt_tags2 = u"OR '/list', %s:quit\r\n : " % (
        term.bold_yellow_underline('Escape'),)
    while True:
        # Accept user input for multiple 'tag's, or /list command
        echo(u'\r\n\r\n')
        echo(prompt_tags1)
        echo(prompt_tags2)
        width = term.width - 6
        sel_tags = u', '.join(msg.tags)
        inp_tags = LineEditor(width, sel_tags).read()
        if inp_tags is not None and 0 == len(inp_tags.strip()):
            # no tags must be (private ..)
            msg.tags = set()
            return True
        if inp_tags is None or inp_tags.strip().lower() == '/quit':
            return False
        elif inp_tags.strip().lower() == '/list':
            # list all available tags, and number of messages
            echo(u'\r\n\r\nTags: \r\n')
            all_tags = sorted(tagdb.items())
            if 0 == len(all_tags):
                echo(u'None !'.center(term.width / 2))
            else:
                echo(Ansi(u', '.join(([u'%s(%d)' % (_key, len(_value),)
                                       for (_key, _value) in all_tags]))
                          ).wrap(term.width - 2))
            continue
        echo(u'\r\n')

        # search input as valid tag(s)
        tags = set([inp.strip().lower() for inp in inp_tags.split(',')])
        err = False
        for tag in tags.copy():
            if not tag in tagdb and not (
                    'sysop' in session.user.groups or
                    'moderator' in session.user.groups):
                tags.remove(tag)
                echo(msg_invalidtag % (term.bold_red(tag),))
                err = True
        if err:
            echo(msg_onlymods)
            continue
        msg.tags = tags
        return True
예제 #2
0
파일: msgpoll.py 프로젝트: hick/x84
def publish_network_messages(net):
    " Push messages to network. "
    from x84.bbs import DBProxy
    from x84.bbs.msgbase import format_origin_line, MSGDB

    log = logging.getLogger(__name__)

    log.debug(u'[{net[name]}] publishing new messages.'.format(net=net))

    queuedb = DBProxy('{0}queues'.format(net['name']), use_session=False)
    transdb = DBProxy('{0}trans'.format(net['name']), use_session=False)
    msgdb = DBProxy(MSGDB, use_session=False)

    # publish each message
    for msg_id in sorted(queuedb.keys(),
                         cmp=lambda x, y: cmp(int(x), int(y))):
        if msg_id not in msgdb:
            log.warn('{net[name]} No such message (msg_id={msg_id})'
                     .format(net=net, msg_id=msg_id))
            del queuedb[msg_id]
            continue

        msg = msgdb[msg_id]

        trans_parent = None
        if msg.parent is not None:
            matches = [key for key, data in transdb.items()
                       if int(data) == msg.parent]

            if len(matches) > 0:
                trans_parent = matches[0]
            else:
                log.warn('{net[name]} Parent ID {msg.parent} '
                         'not in translation-DB (msg_id={msg_id})'
                         .format(net=net, msg=msg, msg_id=msg_id))

        trans_id = push_rest(net=net, msg=msg, parent=trans_parent)
        if trans_id is False:
            log.error('{net[name]} Message not posted (msg_id={msg_id})'
                      .format(net=net['name'], msg_id=msg_id))
            continue

        if trans_id in transdb.keys():
            log.error('{net[name]} trans_id={trans_id} conflicts with '
                      '(msg_id={msg_id})'
                      .format(net=net, trans_id=trans_id, msg_id=msg_id))
            with queuedb:
                del queuedb[msg_id]
            continue

        # transform, and possibly duplicate(?) message ..
        with transdb, msgdb, queuedb:
            transdb[trans_id] = msg_id
            msg.body = u''.join((msg.body, format_origin_line()))
            msgdb[msg_id] = msg
            del queuedb[msg_id]
        log.info('{net[name]} Published (msg_id={msg_id}) => {trans_id}'
                 .format(net=net, msg_id=msg_id, trans_id=trans_id))
예제 #3
0
def publish_network_messages(net):
    """ Push messages to network, ``net``. """
    from x84.bbs import DBProxy
    from x84.bbs.msgbase import format_origin_line, MSGDB

    log = logging.getLogger(__name__)

    log.debug(u'[{net[name]}] publishing new messages.'.format(net=net))

    queuedb = DBProxy('{0}queues'.format(net['name']), use_session=False)
    transdb = DBProxy('{0}trans'.format(net['name']), use_session=False)
    msgdb = DBProxy(MSGDB, use_session=False)

    # publish each message
    for msg_id in sorted(queuedb.keys(),
                         cmp=lambda x, y: cmp(int(x), int(y))):
        if msg_id not in msgdb:
            log.warn('[{net[name]}] No such message (msg_id={msg_id})'
                     .format(net=net, msg_id=msg_id))
            del queuedb[msg_id]
            continue

        msg = msgdb[msg_id]

        trans_parent = None
        if msg.parent is not None:
            matches = [key for key, data in transdb.items()
                       if int(data) == msg.parent]

            if len(matches) > 0:
                trans_parent = matches[0]
            else:
                log.warn('[{net[name]}] Parent ID {msg.parent} '
                         'not in translation-DB (msg_id={msg_id})'
                         .format(net=net, msg=msg, msg_id=msg_id))

        trans_id = push_rest(net=net, msg=msg, parent=trans_parent)
        if trans_id is False:
            log.error('[{net[name]}] Message not posted (msg_id={msg_id})'
                      .format(net=net, msg_id=msg_id))
            continue

        if trans_id in transdb.keys():
            log.error('[{net[name]}] trans_id={trans_id} conflicts with '
                      '(msg_id={msg_id})'
                      .format(net=net, trans_id=trans_id, msg_id=msg_id))
            with queuedb:
                del queuedb[msg_id]
            continue

        # transform, and possibly duplicate(?) message ..
        with transdb, msgdb, queuedb:
            transdb[trans_id] = msg_id
            msg.body = u''.join((msg.body, format_origin_line()))
            msgdb[msg_id] = msg
            del queuedb[msg_id]
        log.info('[{net[name]}] Published (msg_id={msg_id}) => {trans_id}'
                 .format(net=net, msg_id=msg_id, trans_id=trans_id))
예제 #4
0
파일: debug.py 프로젝트: rostob/x84
def x84net_requeue():
    # a message failed to queue for delivery, but hellbeard
    # really wanted to see em, so re-queue.
    from x84.bbs import DBProxy, echo
    from pprint import pformat
    queuedb = DBProxy('x84netqueues')
    with queuedb:
        queuedb['264'] = 1
    echo('-')
    echo(pformat(queuedb.items()))
    echo('-')
예제 #5
0
파일: debug.py 프로젝트: hick/x84
def x84net_requeue():
    # a message failed to queue for delivery, but hellbeard
    # really wanted to see em, so re-queue.
    from x84.bbs import DBProxy, echo
    from pprint import pformat
    queuedb = DBProxy('x84netqueues')
    with queuedb:
        queuedb['264'] = 1
    echo('-')
    echo(pformat(queuedb.items()))
    echo('-')
예제 #6
0
파일: ol.py 프로젝트: rostob/x84
def maybe_expunge_records():
    """ Check ceiling of database keys; trim-to MAX_HISTORY. """
    udb = DBProxy('oneliner')
    expunged = 0
    with udb:
        if len(udb) > MAX_HISTORY + 10:
            contents = DBProxy('oneliner').copy()
            _sorted = sorted(
                ((value, key) for (key, value) in contents.items()),
                key=lambda _valkey: keysort_by_datetime(_valkey[0]))
            for expunged, (_, key) in enumerate(
                    _sorted[len(udb) - MAX_HISTORY:]):
                del udb[key]
    if expunged:
        log = logging.getLogger(__name__)
        log.info('expunged %d records from database', expunged)
예제 #7
0
파일: ol.py 프로젝트: tcummings19/3x84
def maybe_expunge_records():
    """ Check ceiling of database keys; trim-to MAX_HISTORY. """
    udb = DBProxy('oneliner')
    expunged = 0
    with udb:
        if len(udb) > MAX_HISTORY + 10:
            contents = DBProxy('oneliner').copy()
            _sorted = sorted(
                ((value, key) for (key, value) in contents.items()),
                key=lambda _valkey: keysort_by_datetime(_valkey[0]))
            for expunged, (_,
                           key) in enumerate(_sorted[len(udb) - MAX_HISTORY:]):
                del udb[key]
    if expunged:
        log = logging.getLogger(__name__)
        log.info('expunged %d records from database', expunged)
예제 #8
0
def lc_retrieve():
    """
    Returns tuple of ([nicknames,] u'text'), where 'text' describes in Ansi
    color the last callers to the system, and 'nicknames' is simply a list
    of last callers (for lightbar selection key).
    """
    # pylint: disable=R0914
    #         Too many local variables
    from x84.bbs import get_user, ini, timeago, getterminal
    from x84.bbs import DBProxy
    import time
    term = getterminal()
    udb = DBProxy('lastcalls')
    # re-order by time called; unfortunate ..; note that sqlite
    # encodes unicode as utf-8; but doesn't decode it on retrieval,
    # of dict keys; possible upstream patching opportunity here,
    sortdb = {}
    for ((handle), (tm_lc, _nc, origin)) in (udb.items()):
        while tm_lc in sortdb:
            tm_lc += 0.1
        sortdb[tm_lc] = [handle.decode('utf-8'), _nc, origin]

    padd_handle = (ini.CFG.getint('nua', 'max_user') + 2)
    padd_origin = (ini.CFG.getint('nua', 'max_location') + 2)
    rstr = u''
    nicks = []
    for tm_lc, (handle, _nc, origin) in (reversed(sorted(sortdb.items()))):
        try:
            is_sysop = 'sysop' in get_user(handle).groups
        except KeyError:
            # anonymous/deleted accts,
            is_sysop = False
        rstr += (term.bold_red(u'@') if is_sysop else u''
                 ) + (term.ljust(handle,
                     (padd_handle - (2 if is_sysop else 1))))
        rstr += term.red(origin.ljust(padd_origin))
        rstr += timeago(time.time() - tm_lc)
        rstr += u'\n'
        nicks.append(handle)
    return (nicks, rstr.rstrip())
예제 #9
0
def prompt_tags(tags):
    """ Prompt for and return valid tags from TAGDB. """
    # pylint: disable=R0914,W0603
    #         Too many local variables
    #         Using the global statement
    from x84.bbs import DBProxy, echo, getterminal, getsession
    from x84.bbs import Ansi, LineEditor, getch
    session, term = getsession(), getterminal()
    tagdb = DBProxy('tags')
    global FILTER_PRIVATE
    while True:
        # Accept user input for a 'search tag', or /list command
        #
        echo(u"\r\n\r\nENtER SEARCh %s, COMMA-dEliMitEd. " % (
            term.red('TAG(s)'),))
        echo(u"OR '/list', %s%s\r\n : " % (
            (term.yellow_underline('^x') + u':autoscan '
                if session.user.get('autoscan', False) else u''),
            term.yellow_underline('^a') + u':ll msgs ' +
            term.yellow_underline('Esc') + u':quit',))
        width = term.width - 6
        sel_tags = u', '.join(tags)
        while len(Ansi(sel_tags)) >= (width - 8):
            tags = tags[:-1]
            sel_tags = u', '.join(tags)
        lne = LineEditor(width, sel_tags)
        echo(lne.refresh())
        while not lne.carriage_returned:
            inp = getch()
            if inp in (unichr(27), term.KEY_EXIT):
                return None
            if inp in (unichr(24),): # ^A:all
                return set()
            if inp in (unichr(1),): # ^X:autoscan
                return session.user.get('autoscan', set())
            else:
                echo(lne.process_keystroke(inp))
        if lne.carriage_returned:
            inp_tags = lne.content
        if (inp_tags is None or 0 == len(inp_tags)
                or inp_tags.strip().lower() == '/quit'):
            return set()
        elif inp_tags.strip().lower() == '/list':
            # list all available tags, and number of messages
            echo(term.normal)
            echo(u'\r\n\r\nTags: \r\n')
            all_tags = sorted(tagdb.items())
            if 0 == len(all_tags):
                echo(u'None !'.center(term.width / 2))
            else:
                echo(Ansi(u', '.join(([u'%s(%s)' % (
                    term.red(tag),
                    term.yellow(str(len(msgs))),)
                        for (tag, msgs) in all_tags]))).wrap(term.width - 2))
            continue
        elif (inp_tags.strip().lower() == '/nofilter'
                and 'sysop' in session.user.groups):
            # disable filtering private messages
            FILTER_PRIVATE = False
            continue

        echo(u'\r\n')
        # search input as valid tag(s)
        tags = set([_tag.strip().lower() for _tag in inp_tags.split(',')])
        for tag in tags.copy():
            if not tag in tagdb:
                tags.remove(tag)
                echo(u"\r\nNO MESSAGES With tAG '%s' fOUNd." % (
                    term.red(tag),))
        return tags
예제 #10
0
def prompt_tags(msg):
    """ Prompt for and return tags wished for message. """
    # pylint: disable=R0914,W0603
    #         Too many local variables
    #         Using the global statement
    from x84.bbs import DBProxy, echo, getterminal, getsession
    from x84.bbs import LineEditor, ini
    session, term = getsession(), getterminal()
    tagdb = DBProxy('tags')
    # version 1.0.9 introduced new ini option; set defaults for
    # those missing it from 1.0.8 upgrades.
    import ConfigParser
    try:
        moderated_tags = ini.CFG.getboolean('msg', 'moderated_tags')
    except ConfigParser.NoOptionError:
        moderated_tags = False
    try:
        moderated_groups = set(ini.CFG.get('msg', 'tag_moderator_groups'
                                           ).split())
    except ConfigParser.NoOptionError:
        moderated_groups = ('sysop', 'moderator',)
    msg_onlymods = (u"\r\nONlY MEMbERS Of GROUPS %s MAY CREAtE NEW tAGS." % (
        ", ".join(["'%s'".format(term.bold_yellow(grp)
                                 for grp in moderated_groups)])))
    msg_invalidtag = u"\r\n'%s' is not a valid tag."
    prompt_tags1 = u"ENtER %s, COMMA-dEliMitEd. " % (term.bold_red('TAG(s)'),)
    prompt_tags2 = u"OR '/list', %s:quit\r\n : " % (
        term.bold_yellow_underline('Escape'),)
    while True:
        # Accept user input for multiple 'tag's, or /list command
        echo(u'\r\n\r\n')
        echo(prompt_tags1)
        echo(prompt_tags2)
        width = term.width - 6
        sel_tags = u', '.join(msg.tags)
        inp_tags = LineEditor(width, sel_tags).read()
        if inp_tags is not None and 0 == len(inp_tags.strip()):
            # no tags must be (private ..)
            msg.tags = set()
            return True
        if inp_tags is None or inp_tags.strip().lower() == '/quit':
            return False
        elif inp_tags.strip().lower() == '/list':
            # list all available tags, and number of messages
            echo(u'\r\n\r\nTags: \r\n')
            all_tags = sorted(tagdb.items())
            if 0 == len(all_tags):
                echo(u'None !'.center(term.width / 2))
            else:
                echo(u', '.join((term.wrap([u'%s(%d)' % (_key, len(_value),)
                                       for (_key, _value) in all_tags]))
                          ), term.width - 2)
            continue
        echo(u'\r\n')

        # search input as valid tag(s)
        tags = set([inp.strip().lower() for inp in inp_tags.split(',')])

        # if the tag is new, and the user's group is not in
        # tag_moderator_groups, then dissallow such tag if
        # 'moderated_tags = yes' in ini cfg
        if moderated_tags:
            err = False
            for tag in tags.copy():
                if not tag in tagdb and not (
                        session.users.groups & moderated_groups):
                    tags.remove(tag)
                    echo(msg_invalidtag % (term.bold_red(tag),))
                    err = True
            if err:
                echo(msg_onlymods)
                continue
        msg.tags = tags
        return True
예제 #11
0
def prompt_tags(msg):
    """ Prompt for and return tags wished for message. """
    # pylint: disable=R0914,W0603
    #         Too many local variables
    #         Using the global statement
    from x84.bbs import DBProxy, echo, getterminal, getsession
    from x84.bbs import Ansi, LineEditor, ini
    session, term = getsession(), getterminal()
    tagdb = DBProxy('tags')
    # version 1.0.9 introduced new ini option; set defaults for
    # those missing it from 1.0.8 upgrades.
    import ConfigParser
    try:
        moderated_tags = ini.CFG.getboolean('msg', 'moderated_tags')
    except ConfigParser.NoOptionError:
        moderated_tags = False
    try:
        moderated_groups = set(
            ini.CFG.get('msg', 'tag_moderator_groups').split())
    except ConfigParser.NoOptionError:
        moderated_groups = (
            'sysop',
            'moderator',
        )
    msg_onlymods = (
        u"\r\nONlY MEMbERS Of GROUPS %s MAY CREAtE NEW tAGS." % (", ".join(
            ["'%s'".format(term.bold_yellow(grp)
                           for grp in moderated_groups)])))
    msg_invalidtag = u"\r\n'%s' is not a valid tag."
    prompt_tags1 = u"ENtER %s, COMMA-dEliMitEd. " % (term.bold_red('TAG(s)'), )
    prompt_tags2 = u"OR '/list', %s:quit\r\n : " % (
        term.bold_yellow_underline('Escape'), )
    while True:
        # Accept user input for multiple 'tag's, or /list command
        echo(u'\r\n\r\n')
        echo(prompt_tags1)
        echo(prompt_tags2)
        width = term.width - 6
        sel_tags = u', '.join(msg.tags)
        inp_tags = LineEditor(width, sel_tags).read()
        if inp_tags is not None and 0 == len(inp_tags.strip()):
            # no tags must be (private ..)
            msg.tags = set()
            return True
        if inp_tags is None or inp_tags.strip().lower() == '/quit':
            return False
        elif inp_tags.strip().lower() == '/list':
            # list all available tags, and number of messages
            echo(u'\r\n\r\nTags: \r\n')
            all_tags = sorted(tagdb.items())
            if 0 == len(all_tags):
                echo(u'None !'.center(term.width / 2))
            else:
                echo(
                    Ansi(u', '.join(([
                        u'%s(%d)' % (
                            _key,
                            len(_value),
                        ) for (_key, _value) in all_tags
                    ]))).wrap(term.width - 2))
            continue
        echo(u'\r\n')

        # search input as valid tag(s)
        tags = set([inp.strip().lower() for inp in inp_tags.split(',')])

        # if the tag is new, and the user's group is not in
        # tag_moderator_groups, then dissallow such tag if
        # 'moderated_tags = yes' in ini cfg
        if moderated_tags:
            err = False
            for tag in tags.copy():
                if not tag in tagdb and not (session.users.groups
                                             & moderated_groups):
                    tags.remove(tag)
                    echo(msg_invalidtag % (term.bold_red(tag), ))
                    err = True
            if err:
                echo(msg_onlymods)
                continue
        msg.tags = tags
        return True
예제 #12
0
파일: feeds.py 프로젝트: hick/x84
def main(autoscan_tags=None):
    """ Main procedure. """
    # pylint: disable=W0603,R0912
    #         Using the global statement
    #         Too many branches
    from x84.bbs import getsession, getterminal, echo, getch
    from x84.bbs import list_msgs
    session, term = getsession(), getterminal()
    session.activity = 'autoscan msgs'

    ### 尝试
    session.log.info("Hick3")
    ### 获取所有 tag
    tagdb = DBProxy('tags')
    all_tags = sorted(tagdb.items())
    session.log.info(all_tags)
    ### 尝试直接调出显示的 message ,第二个参数应该是标识为未读的
    msg = new = [1, 2, 3, 4,5 ,6, 7, 8,9]
    read_messages(msg, new)
    return

    ### 首先是显示提示输入的 tag 的标签
    echo(banner())
    global ALREADY_READ, SEARCH_TAGS, DELETED
    if autoscan_tags is not None:
        SEARCH_TAGS = autoscan_tags
        echo(u''.join((
            term.bold_black('[ '),
            term.yellow('AUtOSCAN'),
            term.bold_black(' ]'), u'\r\n')))
    ### 默认就往这里了, 提示输入 tag
    else:
        # 默认 tag 为 public
        SEARCH_TAGS = set(['hick3'])
        # also throw in user groups, maybe the top 3 .. ?
        SEARCH_TAGS.update(session.user.groups)
        SEARCH_TAGS = prompt_tags(SEARCH_TAGS)
        # user escape
        if SEARCH_TAGS is None:
            return

    echo(u'\r\n\r\n%s%s ' % (
        term.bold_yellow('SCANNiNG'),
        term.bold_black(':'),))
    echo(u','.join([term.red(tag) for tag in SEARCH_TAGS]
                   if 0 != len(SEARCH_TAGS) else ['<All>', ]))

    ### 直到有选择 tags , 保存到 session.user 中
    if (SEARCH_TAGS != session.user.get('autoscan', None)):
        echo(u'\r\n\r\nSave tag list as autoscan on login [yn] ?\b\b')
        while True:
            inp = getch()
            if inp in (u'q', 'Q', unichr(27), u'n', u'N'):
                break
            elif inp in (u'y', u'Y'):
                session.user['autoscan'] = SEARCH_TAGS
                break

    # retrieve all matching messages,:  list_msgs 根据 tags 获得所有记录,看情形这个信息量大了有问题哈
    all_msgs = list_msgs(SEARCH_TAGS)
    echo(u'\r\n\r\n%s messages.' % (term.yellow_reverse(str(len(all_msgs),))))
    if 0 == len(all_msgs):
        getch(0.5)
        return

    # filter messages public/private/group-tag/new
    ### 分 tag 和是否未读,删除等统计
    ALREADY_READ = session.user.get('readmsgs', set())
    DELETED = session.user.get('trash', set())
    msgs, new = msg_filter(all_msgs)
    if 0 == len(msgs) and 0 == len(new):
        getch(0.5)
        return

    # prompt read 'a'll, 'n'ew, or 'q'uit
    echo(u'\r\n  REAd [%s]ll %d%s message%s [qa%s] ?\b\b' % (
        term.yellow_underline(u'a'),
        len(msgs), (
        u' or %d [%s]EW ' % (
        len(new), term.yellow_underline(u'n'),)
            if new else u''),
        u's' if 1 != len(msgs) else u'',
        u'n' if new else u'',))
    while True:
        inp = getch()
        if inp in (u'q', 'Q', unichr(27)):
            return
        elif inp in (u'n', u'N') and len(new):
            # read only new messages
            msgs = new
            break
        elif inp in (u'a', u'A'):
            break

    # 根君上面的用户选择,读取消息, 某次记录 log  msgs 和 new 都是帖子 id 的 set 
    # read target messages
    # session.log.info(msgs)
    # session.log.info(new)
    read_messages(msgs, new)
예제 #13
0
def main(autoscan_tags=None):
    """ Main procedure. """
    # pylint: disable=W0603,R0912
    #         Using the global statement
    #         Too many branches
    from x84.bbs import getsession, getterminal, echo, getch
    from x84.bbs import list_msgs
    session, term = getsession(), getterminal()
    session.activity = 'autoscan msgs'

    ### 尝试
    session.log.info("Hick3")
    ### 获取所有 tag
    tagdb = DBProxy('tags')
    all_tags = sorted(tagdb.items())
    session.log.info(all_tags)
    ### 尝试直接调出显示的 message ,第二个参数应该是标识为未读的
    msg = new = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    read_messages(msg, new)
    return

    ### 首先是显示提示输入的 tag 的标签
    echo(banner())
    global ALREADY_READ, SEARCH_TAGS, DELETED
    if autoscan_tags is not None:
        SEARCH_TAGS = autoscan_tags
        echo(u''.join((term.bold_black('[ '), term.yellow('AUtOSCAN'),
                       term.bold_black(' ]'), u'\r\n')))
    ### 默认就往这里了, 提示输入 tag
    else:
        # 默认 tag 为 public
        SEARCH_TAGS = set(['hick3'])
        # also throw in user groups, maybe the top 3 .. ?
        SEARCH_TAGS.update(session.user.groups)
        SEARCH_TAGS = prompt_tags(SEARCH_TAGS)
        # user escape
        if SEARCH_TAGS is None:
            return

    echo(u'\r\n\r\n%s%s ' % (
        term.bold_yellow('SCANNiNG'),
        term.bold_black(':'),
    ))
    echo(u','.join([term.red(tag)
                    for tag in SEARCH_TAGS] if 0 != len(SEARCH_TAGS) else [
                        '<All>',
                    ]))

    ### 直到有选择 tags , 保存到 session.user 中
    if (SEARCH_TAGS != session.user.get('autoscan', None)):
        echo(u'\r\n\r\nSave tag list as autoscan on login [yn] ?\b\b')
        while True:
            inp = getch()
            if inp in (u'q', 'Q', unichr(27), u'n', u'N'):
                break
            elif inp in (u'y', u'Y'):
                session.user['autoscan'] = SEARCH_TAGS
                break

    # retrieve all matching messages,:  list_msgs 根据 tags 获得所有记录,看情形这个信息量大了有问题哈
    all_msgs = list_msgs(SEARCH_TAGS)
    echo(u'\r\n\r\n%s messages.' % (term.yellow_reverse(str(len(all_msgs), ))))
    if 0 == len(all_msgs):
        getch(0.5)
        return

    # filter messages public/private/group-tag/new
    ### 分 tag 和是否未读,删除等统计
    ALREADY_READ = session.user.get('readmsgs', set())
    DELETED = session.user.get('trash', set())
    msgs, new = msg_filter(all_msgs)
    if 0 == len(msgs) and 0 == len(new):
        getch(0.5)
        return

    # prompt read 'a'll, 'n'ew, or 'q'uit
    echo(u'\r\n  REAd [%s]ll %d%s message%s [qa%s] ?\b\b' % (
        term.yellow_underline(u'a'),
        len(msgs),
        (u' or %d [%s]EW ' % (
            len(new),
            term.yellow_underline(u'n'),
        ) if new else u''),
        u's' if 1 != len(msgs) else u'',
        u'n' if new else u'',
    ))
    while True:
        inp = getch()
        if inp in (u'q', 'Q', unichr(27)):
            return
        elif inp in (u'n', u'N') and len(new):
            # read only new messages
            msgs = new
            break
        elif inp in (u'a', u'A'):
            break

    # 根君上面的用户选择,读取消息, 某次记录 log  msgs 和 new 都是帖子 id 的 set
    # read target messages
    # session.log.info(msgs)
    # session.log.info(new)
    read_messages(msgs, new)