def main(msg=None): """ Main procedure. """ from x84.bbs import Msg, getsession session = getsession() if msg is None: msg = Msg() msg.tags = ('public',) banner() while True: session.activity = 'Constructing a %s message' % ( u'public' if u'public' in msg.tags else u'private',) if not prompt_recipient(msg): break if not prompt_subject(msg): break if not prompt_public(msg): break if not prompt_body(msg): break # XXX if not prompt_tags(msg): break display_msg(msg) if not prompt_send(): break msg.save() return True return False
def main(msg=None): """ Main procedure. """ from x84.bbs import Msg, getsession session = getsession() if msg is None: msg = Msg() msg.tags = ('public', ) banner() while True: session.activity = 'Constructing a %s message' % ( u'public' if u'public' in msg.tags else u'private', ) if not prompt_recipient(msg): break if not prompt_subject(msg): break if not prompt_tags(msg): break if not prompt_public(msg): break if not prompt_body(msg): break display_msg(msg) if not prompt_send(): break msg.save() return True return False
def sendmsg(sessions): """ Prompt for node and gosub 'writemsg' with recipient set to target user. """ from x84.bbs import gosub, Msg (node, tgt_session) = get_node(sessions) if node is not None: msg = Msg() msg.recipient = tgt_session['handle'] msg.tags.add('private') gosub('writemsg', msg) return True
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
def main(msg=None): """ Main procedure. """ from x84.bbs import Msg, getsession, echo, getterminal from x84.bbs.ini import CFG session, term = getsession(), getterminal() new_message = True if msg is None else False network_tags = None is_network_msg = False if CFG.has_option('msg', 'network_tags'): network_tags = map( str.strip, CFG.get('msg', 'network_tags').split(',')) if new_message: msg = Msg() msg.tags = ('public',) elif network_tags is not None: net_tags = [tag for tag in network_tags if tag in msg.tags] is_network_msg = True if len(net_tags) > 0 else False banner() while True: session.activity = 'Constructing a %s message' % ( u'public' if u'public' in msg.tags else u'private',) if network_tags and new_message: is_network_msg = prompt_network(msg, network_tags) if not prompt_recipient(msg, is_network_msg): break if not prompt_subject(msg): break if not prompt_public(msg): break if not prompt_body(msg): break # XXX if not prompt_tags(msg): break if is_network_msg: # XX move to sub-func how_many = len([tag for tag in network_tags if tag in msg.tags]) if how_many == 0: echo(u''.join((u'\r\n', term.bold_yellow_on_red( u' YOU tOld ME thiS WAS A NEtWORk MESSAGE. ' u'WhY did YOU liE?! '), u'\r\n'))) term.inkey(timeout=7) continue if how_many > 1: echo(u''.join(( u'\r\n', term.bold_yellow_on_red( u' ONlY ONE NEtWORk CAN bE POStEd tO At A tiME, ' u'SORRY '), u'\r\n'))) continue if u'public' not in msg.tags: echo(u''.join((u'\r\n', term.bold_yellow_on_red( u" YOU ShOUldN't SENd PRiVAtE " u"MESSAGES OVER tHE NEtWORk... "), u'\r\n'))) term.inkey(timeout=7) continue display_msg(msg) if not prompt_send(): break msg.save() session.user['msgs_sent'] = session.user.get('msgs_sent', 0) + 1 return True return False
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
def main(quick=False): """ Main procedure. """ session, term = getsession(), getterminal() session.activity = 'checking for new messages' # set syncterm font, if any if term.kind.startswith('ansi'): echo(syncterm_setfont(syncterm_font)) colors = dict(highlight=lambda txt: txt, lowlight=lambda txt: txt, backlight=lambda txt: txt, text=lambda txt: txt) if not colored_menu_items else dict( highlight=getattr(term, color_highlight), lowlight=getattr(term, color_lowlight), backlight=getattr(term, color_backlight), text=getattr(term, color_text)) yloc = top_margin = 0 subscription = session.user.get('msg_subscription', []) dirty = 2 while True: if dirty == 2: # display header art, yloc = display_banner(art_file, encoding=art_encoding, center=True) xloc = max(0, (term.width // 2) - 40) echo(u'\r\n') top_margin = yloc = (yloc + 1) elif dirty: echo(term.move(top_margin, 0) + term.normal + term.clear_eos) echo(term.move(top_margin, xloc)) if dirty: if not subscription: # prompt the user for a tag subscription, and loop # back again when completed to re-draw and show new messages. subscription = session.user['msg_subscription'] = ( prompt_subscription(session=session, term=term, yloc=top_margin, subscription=subscription, colors=colors)) continue messages, messages_bytags = get_messages_by_subscription( session, subscription) # When quick login ('y') selected in top.py, return immediately # when no new messages are matched any longer. if quick and not messages['new']: echo(term.move_x(xloc) + u'\r\nNo new messages.\r\n') return waitprompt(term) txt = describe_message_area(term=term, subscription=subscription, messages_bytags=messages_bytags, colors=colors) yloc = top_margin + show_description( term=term, description=txt, color=None, subsequent_indent=' ' * len('message area: ')) echo( render_menu_entries(term=term, top_margin=yloc, menu_items=get_menu(messages), colors=colors, max_cols=2)) echo(display_prompt(term=term, colors=colors)) echo(colors['backlight'](u' \b')) dirty = False event, data = session.read_events(('refresh', 'newmsg', 'input')) if event == 'refresh': # screen resized, redraw. dirty = 2 continue elif event == 'newmsg': # When a new message is sent, 'newmsg' event is broadcasted. session.flush_event('newmsg') nxt_msgs, nxt_bytags = get_messages_by_subscription( session, subscription) if nxt_msgs['new'] - messages['new']: # beep and re-display when a new message has arrived. echo(u'\b') messages, messages_bytags = nxt_msgs, nxt_bytags dirty = True continue elif event == 'input': # on input, block until carriage return session.buffer_input(data, pushback=True) given_inp = LineEditor(1, colors={ 'highlight': colors['backlight'] }).read() if given_inp is None: # escape/cancel continue inp = given_inp.strip() if inp.lower() in (u'n', 'a', 'v'): # read new/all/private messages message_indices = sorted( list({ 'n': messages['new'], 'a': messages['all'], 'v': messages['private'], }[inp.lower()])) if message_indices: dirty = 2 read_messages(session=session, term=term, message_indices=message_indices, colors=colors) elif inp.lower() == u'm' and messages['new']: # mark all messages as read dirty = 1 do_mark_as_read(session, messages['new']) elif inp.lower() in (u'p', u'w'): # write new public/private message dirty = 2 public = bool(inp.lower() == u'p') msg = Msg() if (not prompt_recipient( term=term, msg=msg, colors=colors, public=public) or not prompt_subject(term=term, msg=msg, colors=colors) or not prompt_body(term=term, msg=msg, colors=colors) or not prompt_tags(session=session, term=term, msg=msg, colors=colors, public=public)): continue do_send_message(session=session, term=term, msg=msg, colors=colors) elif inp.lower() == u'c': # prompt for new tag subscription (at next loop) subscription = [] dirty = 1 elif inp.lower() == u'?': # help echo(term.move(top_margin, 0) + term.clear_eos) do_describe_message_system(term, colors) waitprompt(term) dirty = 2 elif inp.lower() == u'q': return if given_inp: # clear out line editor prompt echo(colors['backlight'](u'\b \b'))
def poll_network_for_messages(net): " pull for new messages of network, storing locally. " from x84.bbs import Msg, DBProxy from x84.bbs.msgbase import to_localtime log = logging.getLogger(__name__) log.debug(u'[{net[name]}] polling for new messages.'.format(net=net)) try: last_msg_id = get_last_msg_id(net['last_file']) except (OSError, IOError) as err: log.error('[{net[name]}] skipping network: {err}'.format(net=net, err=err)) return msgs = pull_rest(net=net, last_msg_id=last_msg_id) if msgs is not False: log.info('{net[name]} Retrieved {num} messages'.format(net=net, num=len(msgs))) else: log.debug('{net[name]} no messages.'.format(net=net)) return transdb = DBProxy('{0}trans'.format(net['name']), use_session=False) transkeys = transdb.keys() msgs = sorted(msgs, cmp=lambda x, y: cmp(int(x['id']), int(y['id']))) # store messages locally, saving their translated IDs to the transdb for msg in msgs: store_msg = Msg() store_msg.recipient = msg['recipient'] store_msg.author = msg['author'] store_msg.subject = msg['subject'] store_msg.body = msg['body'] store_msg.tags = set(msg['tags']) store_msg.tags.add(u''.join((net['name']))) if msg['recipient'] is None and u'public' not in msg['tags']: log.warn("{net[name]} No recipient (msg_id={msg[id]}), " "adding 'public' tag".format(net=net, msg=msg)) store_msg.tags.add(u'public') if (msg['parent'] is not None and str(msg['parent']) not in transkeys): log.warn('{net[name]} No such parent message ({msg[parent]}, ' 'msg_id={msg[id]}), removing reference.'.format(net=net, msg=msg)) elif msg['parent'] is not None: store_msg.parent = int(transdb[msg['parent']]) if msg['id'] in transkeys: log.warn('{net[name]} dupe (msg_id={msg[id]}) discarded.'.format( net=net, msg=msg)) else: # do not save this message to network, we already received # it from the network, set send_net=False store_msg.save(send_net=False, ctime=to_localtime(msg['ctime'])) with transdb: transdb[msg['id']] = store_msg.idx transkeys.append(msg['id']) log.info( '{net[name]} Processed (msg_id={msg[id]}) => {new_id}'.format( net=net, msg=msg, new_id=store_msg.idx)) if 'last' not in net.keys() or int(net['last']) < int(msg['id']): net['last'] = msg['id'] if 'last' in net.keys(): with open(net['last_file'], 'w') as last_fp: last_fp.write(str(net['last'])) return
def poll_network_for_messages(net): " pull for new messages of network, storing locally. " from x84.bbs import Msg, DBProxy from x84.bbs.msgbase import to_localtime log = logging.getLogger(__name__) log.debug(u'[{net[name]}] polling for new messages.'.format(net=net)) try: last_msg_id = get_last_msg_id(net['last_file']) except (OSError, IOError) as err: log.error('[{net[name]}] skipping network: {err}' .format(net=net, err=err)) return msgs = pull_rest(net=net, last_msg_id=last_msg_id) if msgs is not False: log.info('{net[name]} Retrieved {num} messages' .format(net=net, num=len(msgs))) else: log.debug('{net[name]} no messages.'.format(net=net)) return transdb = DBProxy('{0}trans'.format(net['name']), use_session=False) transkeys = transdb.keys() msgs = sorted(msgs, cmp=lambda x, y: cmp(int(x['id']), int(y['id']))) # store messages locally, saving their translated IDs to the transdb for msg in msgs: store_msg = Msg() store_msg.recipient = msg['recipient'] store_msg.author = msg['author'] store_msg.subject = msg['subject'] store_msg.body = msg['body'] store_msg.tags = set(msg['tags']) store_msg.tags.add(u''.join((net['name']))) if msg['recipient'] is None and u'public' not in msg['tags']: log.warn("{net[name]} No recipient (msg_id={msg[id]}), " "adding 'public' tag".format(net=net, msg=msg)) store_msg.tags.add(u'public') if (msg['parent'] is not None and str(msg['parent']) not in transkeys): log.warn('{net[name]} No such parent message ({msg[parent]}, ' 'msg_id={msg[id]}), removing reference.' .format(net=net, msg=msg)) elif msg['parent'] is not None: store_msg.parent = int(transdb[msg['parent']]) if msg['id'] in transkeys: log.warn('{net[name]} dupe (msg_id={msg[id]}) discarded.' .format(net=net, msg=msg)) else: # do not save this message to network, we already received # it from the network, set send_net=False store_msg.save(send_net=False, ctime=to_localtime(msg['ctime'])) with transdb: transdb[msg['id']] = store_msg.idx transkeys.append(msg['id']) log.info('{net[name]} Processed (msg_id={msg[id]}) => {new_id}' .format(net=net, msg=msg, new_id=store_msg.idx)) if 'last' not in net.keys() or int(net['last']) < int(msg['id']): net['last'] = msg['id'] if 'last' in net.keys(): with open(net['last_file'], 'w') as last_fp: last_fp.write(str(net['last'])) return
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
def main(msg=None): """ Main procedure. """ from x84.bbs import Msg, getsession, echo, getterminal from x84.bbs.ini import CFG session, term = getsession(), getterminal() new_message = True if msg is None else False network_tags = None is_network_msg = False if CFG.has_option('msg', 'network_tags'): network_tags = map( str.strip, CFG.get('msg', 'network_tags').split(',')) if CFG.has_option('msg', 'server_tags'): if network_tags is None: network_tags = list() network_tags += map( str.strip, CFG.get('msg', 'server_tags').split(',')) if new_message: msg = Msg() msg.tags = ('public',) elif network_tags is not None: net_tags = [tag for tag in network_tags if tag in msg.tags] is_network_msg = True if len(net_tags) > 0 else False banner() while True: session.activity = 'Constructing a %s message' % ( u'public' if u'public' in msg.tags else u'private',) if network_tags and new_message: is_network_msg = prompt_network(msg, network_tags) if not prompt_recipient(msg, is_network_msg): break if not prompt_subject(msg): break if not prompt_public(msg): break if not prompt_body(msg): break # XXX if not prompt_tags(msg): break if is_network_msg: # XX move to sub-func how_many = len([tag for tag in network_tags if tag in msg.tags]) if how_many == 0: echo(u''.join((u'\r\n', term.bold_yellow_on_red( u' YOU tOld ME thiS WAS A NEtWORk MESSAGE. ' u'WhY did YOU liE?! '), u'\r\n'))) term.inkey(timeout=7) continue if how_many > 1: echo(u''.join(( u'\r\n', term.bold_yellow_on_red( u' ONlY ONE NEtWORk CAN bE POStEd tO At A tiME, ' u'SORRY '), u'\r\n'))) continue if u'public' not in msg.tags: echo(u''.join((u'\r\n', term.bold_yellow_on_red( u" YOU ShOUldN't SENd PRiVAtE " u"MESSAGES OVER tHE NEtWORk... "), u'\r\n'))) term.inkey(timeout=7) continue display_msg(msg) if not prompt_send(): break msg.save() session.user['msgs_sent'] = session.user.get('msgs_sent', 0) + 1 return True return False