def do_fetch(postal): """ Given postal code, fetch and return xml root node of weather results. """ from x84.bbs import echo, getch, getterminal import StringIO term = getterminal() disp_msg('fEtChiNG') resp = requests.get(u'http://apple.accuweather.com' + u'/adcbin/apple/Apple_Weather_Data.asp', params=(('zipcode', postal),)) if resp is None: disp_notfound() return None if resp.status_code != 200: # todo: logger.error echo(u'\r\n') echo(term.bold_red(u'StAtUS COdE: %s' % (resp.status_code,))) echo(u'\r\n\r\n') echo(repr(resp.content)) echo(u'\r\n\r\n' + 'PRESS ANY kEY') getch() return None xml_stream = StringIO.StringIO(resp.content) tree = ET.parse(xml_stream) return tree.getroot()
def refresh(): """ Refresh screen and return top-left (x, y) location. """ # set syncterm font to cp437 if term.kind.startswith('ansi'): echo(syncterm_setfont('cp437')) echo(u'\r\n\r\n') if term.width < width: echo(u''.join(( term.move(term.height, 0), u'\r\n\r\n', term.bold_red + 'screen too thin! (%s/%s)' % ( term.width, width,), u'\r\n\r\n', u'press any key...',))) getch() return (None, None) if term.height < height: echo(u''.join(( term.move(term.height, 0), u'\r\n\r\n', term.bold_red + 'screen too short! (%s/%s)' % ( term.height, height), u'\r\n\r\n', u'press any key...',))) getch() return (None, None) xloc = (term.width / 2) - (width / 2) yloc = (term.height / 2) - (height / 2) echo(u''.join(( term.normal, (u'\r\n' + term.clear_eol) * term.height, u''.join([term.move(yloc + abs_y, xloc) + line for abs_y, line in enumerate(otxt)]),))) return xloc, yloc
def do_search(search): """ Given any arbitrary string, return list of possible matching locations. """ import StringIO from x84.bbs import echo, getch disp_msg(u'SEARChiNG') resp = requests.get(u'http://apple.accuweather.com' + u'/adcbin/apple/Apple_find_city.asp', params=(('location', search),)) locations = list() if resp is None: disp_notfound() elif resp.status_code != 200: # todo: logger.error echo(u'\r\n' + u'Status Code: %s\r\n\r\n' % (resp.status_code,)) echo(repr(resp.content)) echo(u'\r\n\r\n' + 'Press any key') getch() else: # print resp.content xml_stream = StringIO.StringIO(resp.content) locations = list([dict(elem.attrib.items()) for _event, elem in ET.iterparse(xml_stream) if elem.tag == 'location']) if 0 == len(locations): disp_notfound() else: disp_found(len(locations)) return locations
def view_ansi(key): """ fetch and view a bbs ansi. They're not often very good ... """ from x84.bbs import getterminal, echo, DBProxy, ini, getch, from_cp437 term = getterminal() ansiurl = DBProxy('bbslist')[key]['ansi'] logger = logging.getLogger() echo(u'\r\n\r\n') if ansiurl is not None and 0 != len(ansiurl) and ansiurl != 'NONE': usernm = ini.CFG.get('bbs-scene', 'user') passwd = ini.CFG.get('bbs-scene', 'pass') req = requests.get(ansiurl, auth=(usernm, passwd)) if req.status_code != 200: echo(u'\r\n\r\nrequest failed,\r\n') echo(u'%r' % (req.content,)) echo(u'\r\n\r\n(code : %s).\r\n' % (req.status_code,)) echo(u'\r\nPress any key ..') logger.warn('ansiurl request failed: %s' % (ansiurl,)) getch() return ansi_txt = from_cp437(sauce.SAUCE(data=req.content).__str__()) echo(ansi_txt) else: echo('no ansi available (%s)' % (ansiurl,)) # move to bottom of screen and getch echo(u''.join(( term.move(term.height, 0), term.normal, u'\r\n\r\nPRESS ANY kEY ...'),)) getch()
def pak(): """ Press any key prompt. """ from x84.bbs import echo, getch msg_pak = u'PRESS ANY kEY' echo(u'\r\n%s ... ' % (msg_pak,)) getch() return
def dummy_pager(user): """ A dummy selector for profile attributes """ from x84.bbs import getsession, getterminal, echo, Ansi, getch session, term = getsession(), getterminal() plan = user.get('.plan', False) from x84.bbs.ini import CFG def_timeout = CFG.getint('system', 'timeout') menu = ['(c)%-20s - %s' % (u'hARACtER ENCOdiNG', term.bold(session.encoding),), '(t)%-20s - %s' % (u'ERMiNAl tYPE', term.bold(session.env.get('TERM', 'unknown')),), '(h)%-20s - %s' % (u'ERMiNAl hEiGht', term.bold(str(term.height)),), '(w)%-20s - %s' % (u'ERMiNAl WidtH', term.bold(str(term.width)),), '(l)%-20s - %s' % (u'OCAtiON', term.bold(user.location),), '(p)%-20s - %s' % (u'ASSWORd', term.bold_black(u'******'),), '(!)%-20s - %s' % (u'Set SA User Cookie', term.bold(user['sausercookie']),), '(@)%-20s - %s' % (u'Set SA Pass Cookie', term.bold_black(user['sapasscookie']),), '(e)%-20s - %s' % (u'-MAil AddRESS', term.bold(user.email),), # '(!)%-20s - %s' % (u'SA User Cookie', # term.bold(user['sausercookie']),), # '(@)%-70s - %s' % (u'SA Pass Cookie', # term.bold(user['sapasscookie']),), (term.bold('t') + '(i)%-19s - %s' % (u'MEOUt', term.bold( str(user.get('timeout', def_timeout))),)), '(s)%-20s - %s' % (u'YSOP ACCESS', term.bold(u'ENAblEd' if 'sysop' in user.groups else 'diSAblEd')), '(m)%-20s - %s' % (u'ESG', term.bold(u'[%s]' % ('y' if user.get( 'mesg', True) else 'n',),)), '(.)%-20s - %s' % (u'PlAN filE', '%d bytes' % ( len(plan),) if plan else '(NO PlAN.)'), '(x)%-20s - %s' % (u'PERt MOdE', term.bold(u'ENAblEd' if user.get('expert', False) else 'diSAblEd')), '(q)Uit', ] echo(term.normal + term.clear() ) lines = Ansi('\n'.join(menu)).wrap(term.width).splitlines() xpos = max(1, int(term.width / 2) - (40 / 2)) for row, line in enumerate(lines): if row and (0 == row % (term.height - 2)): echo(term.reverse(u'\r\n-- More --')) getch() echo(u'\r\n' + ' ' * xpos + line) echo(u'\r\n\r\n Enter option [ctle.xq]: ') return process_keystroke(getch(), user)
def disp_notfound(): """ Display 'bad request -/- not found in red. """ from x84.bbs import getsession, getterminal, echo, getch term = getterminal() echo(u''.join((u'\r\n\r\n', term.bold(u'bAd REQUESt'), term.bold_red(' -/- '), term.bold('NOt fOUNd.',),))) if not getsession().user.get('expert', False): getch(1.7)
def waitprompt(): # Displays a simple "press enter to continue prompt". Very handy! from x84.bbs import echo, getch, getterminal term = getterminal() echo (term.normal+'\n\r'+term.magenta+'('+term.green+'..'+term.white+ ' press any key to continue '+term.green+'..'+term.magenta+')') getch() echo(term.normal_cursor) return
def disp_notfound(): """ Display 'bad request -/- not found in red. """ from x84.bbs import getsession, getterminal, echo, getch term = getterminal() bad_req = term.bold(u'bAd REQUESt') decorator = term.bold_red(u'-/-') not_found = term.bold('NOt fOUNd.') echo('\r\n\r\n{bad_req} {decorator} {not_found}'.format( bad_req=bad_req, decorator=decorator, not_found=not_found)) if not getsession().user.get('expert', False): getch(1.7)
def main(): """ Main routine. """ from x84.bbs import getsession, getterminal, echo, getch session, term = getsession(), getterminal() session.activity = 'Weather' echo(u'\r\n\r\n') location = session.user.get('location', dict()) while True: search = location.get('postal', u'') disp_search_help() search = get_zipsearch(search) if search is None or 0 == len(search): return # exit locations = do_search(search) if 0 != len(locations): location = (locations.pop() if 1 == len(locations) else chose_location(locations) or dict()) root = do_fetch(location.get('postal')) if root is None: return weather = parse_weather(root) #if False == location_prompt(location, 'WEAthER'): # break disp_weather(weather) if False == location_prompt(location, 'fORECASt'): break forecast = parse_forecast(root) disp_forecast(forecast) echo(u'\r\n') echo(term.yellow_reverse('--ENd Of tRANSMiSSiON--')) getch() break if (sorted(location.items()) != sorted(session.user.get('location', dict()).items())): echo(u''.join((u'\r\n\r\n', term.yellow(u'SAVE lOCAtION'), term.bold_yellow(' ('), term.bold_black(u'PRiVAtE'), term.bold_yellow(') '), term.yellow('? '), term.bold_yellow(u'['), term.underline_yellow(u'yn'), term.bold_yellow(u']'), u': '),)) while True: inp = getch() if inp is None or inp in (u'n', u'N', 'q', 'Q', term.KEY_EXIT): break if inp in (u'y', u'Y', u' ', term.KEY_ENTER): session.user['location'] = location break
def main(ttyfile=u'', peek=False): """ Main procedure. """ from x84.bbs import Lightbar, getch, getsession, getterminal, ini, echo # pylint: disable=R0914,R0915 # Too many local variables # Too many statements ttyplay_exe = ini.CFG.get('ttyplay', 'exe') if not os.path.exists(ttyplay_exe): echo(u'\r\n%s NOt iNStAllEd.\r\n' % (ttyplay_exe,)) getch() return session, term = getsession(), getterminal() # pylint: disable=W0212 # Access to a protected member _record_tty of a client class resume_rec = session._record_tty and session.is_recording if resume_rec: session.stop_recording() session._record_tty = False if 'sysop' in session.user.groups and ttyfile == u'': # pylint: disable=W0212 # Access to a protected member _ttyrec_folder of a client class folder = os.path.dirname(ttyfile) or session._ttyrec_folder pos = None while True: files = sorted([fn for fn in os.listdir(session._ttyrec_folder) if fn.endswith('.ttyrec')]) echo(u'\r\n' * term.height) sel = Lightbar(term.height - 1, term.width - 1, 0, 0) sel.colors['border'] = term.bold_green echo(sel.border() + sel.title('- SElECt A RECORdiNG -')) sel.update([(fname, fname) for fname in files]) if pos is not None: sel.position = pos x_ttyfile = sel.read() if x_ttyfile is None or sel.quit: break pos = sel.position ttyfile = os.path.join(folder, x_ttyfile) playfile(ttyplay_exe, ttyfile, peek) else: playfile(ttyplay_exe, ttyfile, peek) if not session.is_recording and resume_rec: session._record_tty = True session.start_recording() echo(term.move(term.height, 0)) echo(u'\r\n')
def get_centigrade(): """ Blocking prompt for setting C/F preference """ from x84.bbs import getterminal, getsession, echo, getch term = getterminal() session = getsession() echo(u'\r\n\r\n') echo(term.yellow(u'Celcius')) echo(term.bold_yellow(u'(')) echo(term.bold_yellow_reverse(u'C')) echo(term.bold_yellow(u')')) echo(u' or ') echo(term.yellow(u'Fahrenheit')) echo(term.bold_yellow(u'(')) echo(term.bold_yellow_reverse(u'F')) echo(term.bold_yellow(u')')) echo(u'? ') anonymous = bool(session.user.handle == 'anonymous') while True: inp = getch() if inp in (u'c', u'C'): session.user['centigrade'] = True if not anonymous: session.user.save() break elif inp in (u'f', u'F'): session.user['centigrade'] = False if not anonymous: session.user.save() break elif inp in (u'q', u'Q', term.KEY_EXIT): break
def chk_save_location(location): """ Prompt user to save location for quick re-use """ from x84.bbs import getterminal, getsession, echo, getch session, term = getsession(), getterminal() stored_location = session.user.get('location', dict()).items() if (sorted(location.items()) == sorted(stored_location)): # location already saved return if session.user.handle == 'anonymous': # anonymous cannot save preferences return # prompt to store (unsaved/changed) location echo(u'\r\n\r\n') echo(term.yellow(u'Save Location')) echo(term.bold_yellow(u' (')) echo(term.bold_black(u'private')) echo(term.bold_yellow(u') ')) echo(term.yellow(u'? ')) echo(term.bold_yellow(u'[')) echo(term.underline_yellow(u'yn')) echo(term.bold_yellow(u']')) echo(u': ') while True: inp = getch() if inp is None or inp in (u'n', u'N', u'q', u'Q', term.KEY_EXIT): break if inp in (u'y', u'Y', u' ', term.KEY_ENTER): session.user['location'] = location break
def disp_forecast(forecast): """ Display weather forecast. """ from x84.bbs import getterminal, echo, Ansi, getch term = getterminal() lno = 1 echo(u'\r\n') for key in sorted(forecast.keys()): fcast = forecast[key] rstr = u''.join(( term.bold_yellow_underline(fcast['DayCode']), u', ', u'/'.join(fcast['ObsDate'].split('/', 3)[0:2]), term.bold_underline(u':'), u' ', u'%s. ' % ( term.bold( fcast.get('TXT_Long', fcast.get('TXT_Short', u''))),), u'hiGH Of %sF, lOW Of %sF. ' % ( term.bold(fcast.get('High_Temperature')), term.bold(fcast.get('Low_Temperature')),),)) if 0.0 != float(fcast.get('Snow_Amount', '0.0')): rstr += u'SNOW AMOUNt %s iN., ' % ( term.yellow(fcast.get('Snow_Amount')),) if 0.0 != float(fcast.get('Rain_Amount', '0.0')): rstr += u'RAiN AMOUNt %s iN., ' % ( term.yellow(fcast.get('Snow_Amount')),) if 0.0 != float(fcast.get('Precip_Amount', '0.0')): rstr += u'PRECiPiTAtiON %s iN., ' % ( term.yellow(fcast.get('Precip_Amount')),) if 0 != int(fcast.get('TStorm_Prob', '0')): rstr += u'thUNdERStORM PRObAbilitY: %s, ' % ( term.yellow(fcast.get('TStorm_Prob')),) if 0 != len(fcast.get('WindDirection', u'')): rstr += u'WiNdS %s ' % ( term.yellow(fcast.get('WindDirection')),) if 0 != len(fcast.get('WindSpeed', u'')): rstr += u'At %sMPh, ' % ( term.yellow(fcast.get('WindSpeed')),) if 0 != len(fcast.get('WindGust', u'')): rstr += u'GUStS Of %sMPh. ' % ( term.yellow(fcast.get('WindGust')),) if 0 != len(fcast.get('Real_Feel_High', u'')): rstr += u'PROdUCiNG ' if 0 != len(fcast.get('Real_Feel_High', u'')): rstr += u'A WiNdCHill HiGh Of %sF, lOW %sF. ' % ( term.yellow( fcast.get('Real_Feel_High')), term.yellow( fcast.get('Real_Feel_Low', u'?')),) echo(u'\r\n') lno += 1 lines = Ansi(rstr).wrap(min(60, int(term.width * .8))).splitlines() for line in lines: lno += 1 echo(line + u'\r\n') if 0 == lno % (term.height - 1): echo(term.yellow_reverse('--MORE--')) if getch() is None: return False echo(u'\r\n')
def get_centigrade(): """ Blocking prompt for setting C/F preference """ from x84.bbs import getterminal, getsession, echo, getch term = getterminal() session = getsession() echo(u'\r\n\r\n') echo(term.yellow(u'CElCiUS')) echo(term.bold_yellow(u'(')) echo(term.bold_yellow_reverse(u'C')) echo(term.bold_yellow(u')')) echo(u' or ') echo(term.yellow(u'fAhRENhEit')) echo(term.bold_yellow(u'(')) echo(term.bold_yellow_reverse(u'F')) echo(term.bold_yellow(u')')) echo('? ') while True: inp = getch() if inp in (u'c', u'C'): session.user['centigrade'] = True session.user.save() break elif inp in (u'f', u'F'): session.user['centigrade'] = False session.user.save() break elif inp in (u'q', u'Q', term.KEY_EXIT): break
def displayfile(filename): term = getterminal() echo(term.clear + term.move(0, 0) + term.normal) text = {} counter = 0 offset = 0 keypressed = '' # the string array named text will be zerobased for line in showart(filename): text[counter] = line counter = counter + 1 while True: echo(term.move(0, 0) + term.normal) # -2 om man vill spara en rad i botten for i in range(0, term.height - 1): if len(text) > i + offset: echo(term.clear_eol + u'\r' + text[i + offset]) keypressed = getch() echo(term.hide_cursor) if keypressed == 'q' or keypressed == 'Q' or keypressed == term.KEY_ESCAPE or keypressed == term.KEY_ENTER: break if keypressed == term.KEY_HOME: offset = 0 if keypressed == term.KEY_END: # if the textline has fewer lines than the screen.. if len(text) < term.height: offset = 0 else: offset = len(text) - term.height + 1 if keypressed == term.KEY_DOWN: # offset < len(text) + term.height: if len(text) > offset + term.height - 1: offset = offset + 1 if keypressed == term.KEY_UP: if offset > 0: offset = offset - 1 if keypressed == term.KEY_LEFT or keypressed == term.KEY_PGUP: if offset > term.height: offset = offset - term.height + 2 else: offset = 0 if keypressed == term.KEY_RIGHT or keypressed == term.KEY_PGDOWN: if (offset + term.height * 2) - 1 < len(text): offset = offset + term.height - 2 else: # if the textline has fewer lines than the screen.. if len(text) < term.height: offset = 0 else: offset = len(text) - term.height + 1
def chk_save_location(location): """ Prompt user to save location for quick re-use """ from x84.bbs import getterminal, getsession, echo, getch session, term = getsession(), getterminal() stored_location = session.user.get('location', dict()).items() if (sorted(location.items()) == sorted(stored_location)): return # prompt to store (unsaved/changed) location echo(u'\r\n\r\n') echo(term.yellow(u'SAVE lOCAtION')) echo(term.bold_yellow(' (')) echo(term.bold_black(u'PRiVAtE')) echo(term.bold_yellow(') ')) echo(term.yellow('? ')) echo(term.bold_yellow(u'[')) echo(term.underline_yellow(u'yn')) echo(term.bold_yellow(u']')) echo(u': ') while True: inp = getch() if inp is None or inp in (u'n', u'N', u'q', u'Q', term.KEY_EXIT): break if inp in (u'y', u'Y', u' ', term.KEY_ENTER): session.user['location'] = location break
def add_comment(key): """ Prompt user to add a comment about a bbs. """ # pylint: disable=R0914 # Too many local variables. from x84.bbs import getsession, getterminal, echo, DBProxy, LineEditor from x84.bbs import getch session, term = getsession(), getterminal() prompt_comment = u'\r\n\r\nWhAt YOU GOt tO SAY? ' prompt_chg = u'\r\n\r\nChANGE EXiStiNG ? [yn] ' echo(term.move(term.height, 0)) echo(prompt_comment) comment = LineEditor(max(10, term.width - len(prompt_comment) - 5)).read() if comment is None or 0 == len(comment.strip()): return new_entry = (session.handle, comment) comments = DBProxy('bbslist', 'comments') comments.acquire() existing = comments[key] if session.handle in (_nick for (_nick, _cmt) in comments[key]): # change existing comment, echo(prompt_chg) if getch() not in (u'y', u'Y'): comments.release() return # re-define list without existing entry, + new entry comments[key] = [(_enick, _ecmt) for (_enick, _ecmt) in existing if session.handle != _enick] + [new_entry] comments.release() return # re-define as existing list + new entry comments[key] = existing + [new_entry] comments.release()
def prompt_ok(): """ Prompt user to continue, True if they select yes. """ from x84.bbs import getsession, getterminal, echo, getch, Selector session, term = getsession(), getterminal() prompt_confirm = u'EVERYthiNG lOOk Ok ?' prompt_continue = u'YES (CONtiNUE)' prompt_chg = u'NO! (ChANGE)' def prompt_ok_dumb(): """ Dummy terminal prompt for confirm/cancel. """ echo('\r\n\r\n%s\r\n' % (prompt_confirm,)) echo('1 - %s\r\n' % (prompt_continue,)) echo('2 - %s\r\n\r\n' % (prompt_chg,)) echo('select (1, 2) --> ') while True: inp = getch() if inp == u'1': return True elif inp == u'2': return False if session.env.get('TERM') == 'unknown': return prompt_ok_dumb() sel = Selector(yloc=term.height - 1, xloc=5, width=term.width - 10, left=prompt_continue, right=prompt_chg) echo(term.normal) echo(term.move(term.height - 2, 0) + term.clear_eol) echo(prompt_confirm.center(term.width - 1) + '\r\n') echo(term.clear_eol + sel.refresh()) while True: echo(sel.process_keystroke(getch())) if sel.selected: return True if sel.selection == prompt_continue else False
def yes_no(lightbar, msg, prompt_msg='are you sure? ', attr=None): """ Prompt user for yes/no, returns True for yes, False for no. """ term = getterminal() keyset = { 'yes': (u'y', u'Y'), 'no': (u'n', u'N'), } echo(u''.join(( lightbar.border(), lightbar.pos(lightbar.yloc + lightbar.height - 1, lightbar.xpadding), msg, u' ', prompt_msg,))) sel = Selector(yloc=lightbar.yloc + lightbar.height - 1, xloc=term.width - 25, width=18, left='Yes', right=' No ') sel.colors['selected'] = term.reverse_red if attr is None else attr sel.keyset['left'].extend(keyset['yes']) sel.keyset['right'].extend(keyset['no']) echo(sel.refresh()) while True: inp = getch() echo(sel.process_keystroke(inp)) if((sel.selected and sel.selection == sel.left) or inp in keyset['yes']): # selected 'yes', return True elif((sel.selected or sel.quit) or inp in keyset['no']): # selected 'no' return False
def main(): """ Main procedure. """ # pylint: disable=R0912 # Too many branches from x84.bbs import getsession, getch, goto, gosub session = getsession() inp = -1 dirty = True while True: if dirty or session.poll_event('refresh'): refresh() inp = getch(1) dirty = True if inp == u'*': goto('main') # reload main menu using hidden option '*' elif inp == u'b': gosub('bbslist') elif inp == u'l': gosub('lc') elif inp == u'o': gosub('ol') elif inp == u's': gosub('si') elif inp == u'u': gosub('userlist') elif inp == u'w': gosub('online') elif inp == u'n': gosub('news') elif inp == u'f': gosub('weather') elif inp == u'e': gosub('profile') elif inp == u'#': gosub('lord') elif inp == u't': gosub('tetris') elif inp == u'c': gosub('chat') elif inp == u'p': gosub('writemsg') elif inp == u'r': gosub('readmsgs') elif inp == u'g': goto('logoff') elif inp == u'!': gosub('charset') elif inp == '\x1f' and 'sysop' in session.user.groups: # ctrl+_, run a debug script gosub('debug') elif inp == u'v' and 'sysop' in session.user.groups: # video cassette player gosub('ttyplay') else: dirty = False
def read(self): """ Reads input until ESCAPE key is pressed (Blocking). Returns None. """ from x84.bbs import getch from x84.bbs.output import echo self._quit = False echo(self.refresh()) while not self.quit: echo(self.process_keystroke(getch()))
def main(file=None, invert=False, Showart=True): """ Main procedure. """ if file != None: displayfile(file) return session = getsession() term = getterminal() session.activity = 'Reading text files' currentfolder = [] stored_lbar_pos = [] filelist = getfilelist(STARTFOLDER) dirty = True inp = None lbar = Lightbar(height = 0, width = 0, xloc = 0, yloc = 0, colors={'highlight': term.bold_white_on_red}) # sets up a lightbar. update_lightbar will give it it's actual values. echo(term.clear+banner()+term.hide_cursor) while 1: if dirty: update_lightbar(lbar,term,filelist) dirty = False while not inp: inp = getch(0.2) if session.poll_event('refresh'): echo(term.clear) if term.width > 79: echo(banner()) update_lightbar(lbar,term,filelist) dirty = True lbar.process_keystroke(inp) if lbar.quit: echo(term.normal_cursor) return echo(lbar.refresh_quick()) if inp == term.KEY_ENTER: if lbar.selection[1] == '( .. ) GO BACK' or os.path.isdir(STARTFOLDER+u''.join(currentfolder)+lbar.selection[0]): if lbar.selection[1] == '( .. ) GO BACK': del currentfolder[-1] filelist = getfilelist(STARTFOLDER+u''.join(currentfolder)) lbar.update(filelist) lbar.goto(stored_lbar_pos[-1]) del stored_lbar_pos[-1] else: currentfolder.append(lbar.selection[0]+u'/') stored_lbar_pos.append(lbar.index) filelist = getfilelist(STARTFOLDER+u''.join(currentfolder)) lbar.goto(0) if len(currentfolder) > 0: filelist.insert(0,['( .. ) GO BACK','( .. ) GO BACK']) lbar.update(filelist) else: displayfile(STARTFOLDER+u''.join(currentfolder)+lbar.selection[0]) echo(term.clear+banner()) dirty = True inp = None
def read(self): """ Reads input until the ENTER or ESCAPE key is pressed (Blocking). """ from x84.bbs import getch from x84.bbs.output import echo self._selected = False self._quit = False echo(self.refresh()) while not (self.selected or self.quit): echo(self.process_keystroke(getch()) or u'') if self.quit: return None return self.selection
def prompt_ok_dumb(): """ Dummy terminal prompt for confirm/cancel. """ echo('\r\n\r\n%s\r\n' % (prompt_confirm,)) echo('1 - %s\r\n' % (prompt_continue,)) echo('2 - %s\r\n\r\n' % (prompt_chg,)) echo('select (1, 2) --> ') while True: inp = getch() if inp == u'1': return True elif inp == u'2': return False
def chose_location_dummy(locations): """ dummy pager for chosing a location """ from x84.bbs import getterminal, echo, getch, LineEditor term = getterminal() msg_enteridx = ( term.bold_yellow(u'('), term.underline_yellow(u'0'), term.yellow(u'-'), term.underline_yellow(u'%d' % (len(locations) - 1,)), term.yellow(u','), term.underline_yellow('Escape'), term.bold_white(u':'), term.yellow('EXit'), term.bold_yellow(u')'), u' ', term.reverse_yellow(':'),) max_nwidth = len('%d' % (len(locations) - 1,)) def disp_entry(num, loc): """ Display City, State. """ return u''.join(( term.bold_yellow(u'['), u'%*d' % (max_nwidth, num), term.bold_yellow(u']'), u' ', term.yellow(loc['city']), u', ', term.yellow(loc['state']), u'\r\n',)) echo(u'\r\n\r\n') lno = 3 for num, loc in enumerate(locations): echo(disp_entry(num, loc)) lno += 1 if lno != 0 and (0 == lno % (term.height)): echo(term.yellow_reverse('--MORE--')) if getch() is None: break echo(u'\r\n') lno += 1 idx = u'' while True: echo(u'\r\n' + u''.join(msg_enteridx)) idx = LineEditor(width=max_nwidth, content=idx).read() if idx is None or len(idx) == 0: return None try: int_idx = int(idx) except ValueError as err: echo(term.bold_red(u'\r\n%s' % (err,))) continue if int_idx < 0 or int_idx > len(locations) - 1: echo(term.bold_red(u'\r\nValue out of range')) continue return locations[int_idx]
def try_reset(user): """ Prompt for password reset. """ from x84.bbs import echo, getch, gosub prompt_reset = u'RESEt PASSWORD (bY E-MAil)? [yn]' echo(prompt_reset) while True: inp = getch() if inp in (u'y', u'Y'): return gosub('pwreset', user.handle) elif inp in (u'n', u'N'): echo(u'\r\n\r\n') return False
def warning(msg, cpsec=10.0, min_sec=3.0, split_loc=3): """ Display a 2-tone warning to user with a dynamic pause """ from x84.bbs import getterminal, echo, getch term = getterminal() echo(u''.join((term.clear_eol, term.normal, u'\r\n\r\n', term.bold_red, msg[:- split_loc], term.normal, msg[ -split_loc:], term.bold_black, u'!'))) inp = getch(max(min_sec, float(len(msg)) / cpsec)) return inp
def merge_mystic(): """ Example script to merge csv records into userbase. """ # pylint: disable=R0914 # Too many local variables from x84.bbs import ini, echo, getch, User, get_user, find_user import os # you must modify variable ``do_write`` to commit changes, # csv format; 'user:pass:origin:email\n', in iso8859-1 encoding. do_write = False inp_file = os.path.join( ini.CFG.get('system', 'datapath'), 'mystic_dat.csv') lno = 0 for lno, line in enumerate(open(inp_file, 'r')): handle = line.split(':', 1)[0].strip().decode('iso8859-1') attrs = line.rstrip().split(':')[2:] (_password, _location, _email) = attrs (_password, _location, _email) = ( _password.strip().decode('iso8859-1'), _location.strip().decode('iso8859-1'), _email.strip().decode('iso8859-1')) echo(u''.join((u'\r\n', handle, u': ', '%d ' % (len(_password)), '%s ' % (_location), '%s ' % (_email),))) match = find_user(handle) if match is None: user = User(handle) user.location = _location user.email = _email user.password = _password else: user = get_user(match) user.groups.add('old-school') if do_write: user.save() echo(u'\r\n\r\n%d lines processed.' % (lno,)) getch()
def ynprompt(): term = getterminal() echo (term.magenta+' (' + term.cyan + 'yes'+term.magenta+'/'+term.cyan+'no' + term.magenta+')'+ term.white) while 1: svar = getch() if (svar == 'y') or (svar == 'Y'): yn = True echo (u' yes!') break if (svar == 'n') or (svar == 'N') or (svar == 'q') or (svar == 'Q'): yn = False echo (u' no') break return(yn)
def read_messages(msgs, title, currentpage, totalpages): """ 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 "YOSPOS" 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', 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))) 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_to = msgs[idx][1] 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 = 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
def play(): import time from random import randint import os from x84.bbs import getterminal, getch, from_cp437, AnsiWindow from x84.bbs import echo as echo_unbuffered term = getterminal() field = [] global charcache charcache = u'' field_width = 10 field_height = 20 # Access scheme looks like this: # layout[p][r][ypox][xpos] # layoutcolor = [ 7,2,3,4,4,6,7 ] layout = [ # ## # ## [ [ [ 1, 1, ], [ 1, 1, ], ], ], # # # # # # # # [[ [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], ], [ [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0], ]], # ### # # [ [ [0, 0, 0], [1, 1, 1], [0, 1, 0], ], [ [0, 1, 0], [0, 1, 1], [0, 1, 0], ], [ [0, 1, 0], [1, 1, 1], [0, 0, 0], ], [ [0, 1, 0], [1, 1, 0], [0, 1, 0], ], ], # # # # # ## [ [ [0, 1, 0], [0, 1, 0], [0, 1, 1], ], [ [0, 0, 1], [1, 1, 1], [0, 0, 0], ], [ [1, 1, 0], [0, 1, 0], [0, 1, 0], ], [ [0, 0, 0], [1, 1, 1], [1, 0, 0], ], ], # # # # # ## [ [ [0, 1, 0], [0, 1, 0], [1, 1, 0], ], [ [0, 0, 0], [1, 1, 1], [0, 0, 1], ], [ [0, 1, 1], [0, 1, 0], [0, 1, 0], ], [ [1, 0, 0], [1, 1, 1], [0, 0, 0], ], ], # ## # ## [ [ [0, 1, 0], [1, 1, 0], [1, 0, 0], ], [ [0, 0, 0], [1, 1, 0], [0, 1, 1], ], ], # ## # ## [ [ [0, 1, 0], [0, 1, 1], [0, 0, 1], ], [ [0, 0, 0], [0, 1, 1], [1, 1, 0], ], ], ] fieldx1 = 32 fieldy1 = 10 scorex1 = 11 scorey1 = 11 class RectRedraw: x1 = None y1 = None x2 = None y2 = None def max(r, val, valmax): if val > valmax: return valmax return val def min(r, val, valmin): if val < valmin: return valmin return val def merge(r, x1, y1, x2, y2): if r.x1 == None or r.x1 > x1: r.x1 = r.min(x1, 0) if r.y1 == None or r.y1 > y1: r.y1 = r.min(y1, 0) if r.x2 == None or r.x2 < x2: r.x2 = r.max(x2, field_width) if r.y2 == None or r.y2 < y2: r.y2 = r.max(y2, field_height) # print r.x1,r.y1,r.x2,r.y2 def clean(r): r.x1 = None r.y1 = None r.x2 = None r.y2 = None rr = RectRedraw() for i in range(field_height): field.append([0] * field_width) def echo(s): global charcache charcache += s assert term.height > (field_height + 1) echo_unbuffered(u''.join(( u'\r\n\r\n', u'REAdY YOUR tERMiNAl %s ' % (term.bold_blue('(!)'), ), u'\r\n\r\n', u'%s PRESS ANY kEY' % (term.bold_black('...'), ), ))) getch() artfile = os.path.join(os.path.dirname(__file__), 'tetris.ans') echo_unbuffered(u'\r\n' * term.height) # cls if os.path.exists(artfile): echo_unbuffered(from_cp437(open(artfile).read()).rstrip()) def gotoxy(x, y): echo(term.move(y, x)) def plotblock(color, lastcolor): if color: c = u'\u2588\u2588' # '\xDB\xDB' else: # both empty c = ' ' color = 0 # Output optimization if color % 8 == 0: color = color / 8 if color == lastcolor: echo(c) else: if color: fg = str(30 + color % 8) else: fg = '37' if color >= 8: bg = ';%d' % (40 + color / 8) else: bg = '' echo('\33[0;' + fg + bg + 'm') echo(c) lastcolor = color return lastcolor def redrawfieldbig(rr): # rr.merge(0,0,field_width,field_height) lastcolor = '' if rr.x1 == None or rr.y1 == None: return # Only draw the parts which have been marked by the # redraw rectangle for y in range(rr.y1, rr.y2): gotoxy(field_width + rr.x1 * 2, 2 + y) for x in range(rr.x1, rr.x2): lastcolor = plotblock(field[y][x], lastcolor) echo(term.normal) rr.clean() def drawfieldbig(): lastcolor = '' for y in range(0, field_height): gotoxy(field_width, 2 + y) for x in range(field_width): lastcolor = plotblock(field[y][x], lastcolor) echo(term.normal) def drawfield(): lastcolor = '' for y in range(0, field_height, 2): # gotoxy(field_width,2+y/2) gotoxy(fieldx1 + 2, fieldy1 + 1 + y / 2) # Which block to show, full, half-up, half-down or empty. for x in range(field_width): color = field[y][x] + field[y + 1][x] * 8 if field[y][x] and field[y + 1][x]: c = u'\u2588' # '\xDB' if field[y][x] == field[y + 1][x]: color = color % 8 else: c = u'\u2580' # '\xDF' elif field[y][x] and not field[y + 1][x]: c = u'\u2580' # '\xDF' elif not field[y][x] and field[y + 1][x]: c = u'\u2584' # '\xDC' else: # both empty c = ' ' # Output optimization if color % 8 == 0: color = color / 8 if color == lastcolor: echo(c) else: if color: fg = str(30 + color % 8) else: fg = '37' if color >= 8: bg = ';%d' % (40 + color / 8) else: bg = '' echo('\33[0;' + fg + bg + 'm') echo(c) lastcolor = color echo(term.normal) layoutcolor = [7, 2, 7, 6, 3, 6, 3] # p = -1 # Current piece type nextpiece = randint(0, len(layout) - 1) p = randint(0, len(layout) - 1) p = 1 r = 0 # Current rotation xpos = 4 # X position # ypos = -2 # Y position ypos = -len(layout[p][0]) level = 1 score = 0 lines = 0 def flush(): global charcache echo_unbuffered(charcache) charcache = u'' def fillpiece(x, y, p, r, value): row = 0 for line in layout[p][r]: col = 0 for c in line: if c and (y + row) >= 0: field[y + row][x + col] = value col += 1 row += 1 def showpiece(x, y, p, r): fillpiece(x, y, p, r, layoutcolor[p]) def hidepiece(): fillpiece(xpos, ypos, p, r, 0) def testpiece(x, y, newr): hidepiece() # Space at the new location? row = 0 for line in layout[p][newr]: col = 0 for c in line: try: if c: if ((y + row) >= 0 and field[y + row][x + col] or (x + col) < 0 or (x + col) > 9): return 0 except IndexError: return 0 col += 1 row += 1 # Movement possible return 1 def movepiece(x, y, newr): if testpiece(x, y, newr): # Build redraw rectangle rr.merge(xpos, ypos, xpos + len(layout[p][0][0]), ypos + len(layout[p][0])) rr.merge(x, y, x + len(layout[p][0][0]), y + len(layout[p][0])) showpiece(x, y, p, newr) return (x, y, newr, 1) else: showpiece(xpos, ypos, p, r) return (xpos, ypos, r, 0) def shownext(p): r = 0 lastcolor = '' for y in range(6): gotoxy(38, 1 + y) for x in range(6): if y == 0 or y == 5 or x == 0 or x == 5: echo('\xB0\xB0') else: echo('\33[0m ') lastcolor = '' for y in range(len(layout[p][r])): gotoxy(40, 2 + y) for x in range(len(layout[p][r][0])): # plotblock(layoutcolor[layout[p][r][y][x]],lastcolor) plotblock(layout[p][r][y][x], lastcolor) def drawstats(): echo(term.move(scorey1, scorex1) + '%d' % level) echo(term.move(scorey1 + 2, scorex1) + '%d' % lines) echo(term.move(scorey1 + 3, scorex1) + '%d' % score) drawstats() ticksize = 0.4 nexttick = time.time() + ticksize showpiece(xpos, ypos, p, r) # shownext(nextpiece) # Full redraw first frame rr.merge(0, 0, field_width, field_height) buf = '' while 1: drawfield() # gotoxy(0,0) # echo('\33[37mx: %d, y: %d, p: %d '%(xpos,ypos,p)) slice = nexttick - time.time() if slice < 0: slice = 0 echo(buf) buf = '' flush() key = getch(slice + 0.01) now = time.time() # hidepiece() if key is not None: if key in (u'q', u'Q'): return (0, 0, 0) elif key in ( term.KEY_LEFT, u'h', ): xpos, ypos, r, m = movepiece(xpos - 1, ypos, r) elif key in ( term.KEY_RIGHT, u'l', ): xpos, ypos, r, m = movepiece(xpos + 1, ypos, r) elif key in ( term.KEY_UP, u'k', ): xpos, ypos, r, m = movepiece(xpos, ypos, (r + 1) % len(layout[p])) elif key in ( term.KEY_DOWN, u'j', ): xpos, ypos, r, m = movepiece(xpos, ypos + 1, r) elif key in (' ', ): m = True c = 0 while m: xpos, ypos, r, m = movepiece(xpos, ypos + 1, r) if m: c += 1 if c: nexttick = time.time() + ticksize # New tick? if now > nexttick: nexttick += ticksize # Move down piece xpos, ypos, r, moved = movepiece(xpos, ypos + 1, r) # Piece has touched down? if not moved: # Is the player dead? if ypos <= -len(layout[p][0]): death_win = AnsiWindow(height=6, width=40, yloc=fieldy1 + 10 / 2, xloc=fieldx1 - 11) death_win.colors['border'] = term.bold_black echo_unbuffered(death_win.clear() + death_win.border()) echo_unbuffered( term.move(fieldy1 + 10 / 2 + 1, fieldx1 - 11)) echo_unbuffered((u'!! gAME OVeR!! Score was: %i' % (score, )).center(40)) echo_unbuffered( term.move(fieldy1 + 10 / 2 + 3, fieldx1 - 11)) echo_unbuffered(u'press RETURN'.center(40)) while True: inp = getch() if inp in (u'\r', term.KEY_ENTER): break return (score, level, lines) # Any complete rows to remove? complete = [] for y in range(field_height): x = 0 while x < field_width: if field[y][x] == 0: break x += 1 if x == field_width: complete.append(y) if len(complete) > 0: # Add score lines += len(complete) score += len(complete) * len(complete) * 100 # Shrink field for line in complete: del field[line] field.insert(0, [0] * field_width) if lines >= level * 10: level += 1 ticksize = 0.4 - level * 0.02 drawstats() # Redraw complete field rr.merge(0, 0, field_width, field_height) # Time for a new piece p = nextpiece nextpiece = randint(0, len(layout) - 1) r = 0 xpos = 4 ypos = -len(layout[p][0]) showpiece(xpos, ypos, p, r)
def show_scores(my_score): from x84.bbs import DBProxy, Pager, getterminal from x84.bbs import getch, echo, getsession, ini session, term = getsession(), getterminal() allscores = DBProxy('tetris').items() if 0 == len(allscores): return # line up over tetris game, but logo & 'made by jojo' in view # -- since we have so much screen width, columize the scores, # the math brings it out to 2 columns, but fmt is adjustable pager_title = term.blue_reverse_underline('- hiGh SCOREs -') len_handle = ini.CFG.getint('nua', 'max_user') score_fmt = u'%s %s %s %s' len_pos = 2 len_score = 10 len_level = 3 height, width = 11, 73 yloc, xloc = 10, 3 pager = Pager(height=height, width=width, yloc=yloc, xloc=xloc) pager.xpadding = 1 pager.glyphs['left-vert'] = u'' pager.glyphs['right-vert'] = u'' pager.colors['border'] = term.blue_reverse pager.alignment = 'center' # pre-fesh pager border before fetch echo(pager.border() + pager.title(pager_title)) highscores = sorted([(_score, _level, _handle.decode('utf8')) for (_handle, (_score, _level, _lines)) in allscores], reverse=True) pager.append(score_fmt % (term.bold_blue_underline('No'.ljust(len_pos)), term.bold_blue_underline('SCORE'.ljust(len_score)), term.bold_blue_underline('lVl'.ljust(len_level)), term.bold_blue_underline('hANdlE'.rjust(len_handle), ))) for pos, (_score, _level, _handle) in enumerate(highscores): if _handle == session.user.handle: pager.append(score_fmt % ( term.blue_reverse(str(pos + 1).ljust(len_pos)), term.blue_reverse(str(_score).ljust(len_score)), term.blue_reverse(str(_level).ljust(len_level)), term.blue_reverse(_handle.rjust(len_handle)), )) else: pager.append(score_fmt % ( term.bold_blue(str(pos + 1).ljust(len_pos)), term.blue(str(_score).ljust(len_score)), term.blue(str(_level).ljust(len_level)), _handle.rjust(len_handle), )) # append additional empty slot rows while len(pager.content) < pager.visible_height: pos += 1 pager.append(score_fmt % ( term.bold_blue(str(pos + 1).ljust(len_pos)), term.bold_black(u'.'.ljust(len_score)), term.bold_black(u'.'.ljust(len_level)), term.bold_black(u'.'.rjust(len_handle)), )) dirty = 2 # 2=do not refresh border & title pager.move_home() while not pager.quit: # 1=full refresh dirty = 1 if session.poll_event('refresh') else dirty if dirty: echo(u''.join(( term.normal, ((pager.border() + pager.title(pager_title)) if dirty != 2 else u''), pager.refresh(), pager.footer(u''.join( (term.underline_blue('q'), term.bold_blue('uit')))), ))) dirty = 0 echo(pager.process_keystroke(getch(1)))
def main(): """ Main procedure. """ from x84.bbs import getterminal, getsession, getch, goto, gosub from x84.bbs import ini, echo from ConfigParser import Error as ConfigError key_map = { '$': 'bulletins', 'n': 'news', 'p': 'writemsg', 'r': 'readmsgs', 'c': 'chat', 'i': 'ircchat', 'l': 'lc', 'o': 'ol', 'b': 'bbslist', 'f': 'weather', 't': 'tetris', 'w': 'online', '!': 'charset', 's': 'si', 'u': 'userlist', 'e': 'profile', 'x': 'main', 'g': 'logoff', '#': 'lord' } # add LORD to menu only if enabled, session, term = getsession(), getterminal() session.activity = u'Lightbar Main menu' echo(term.clear) show_banner() lb = lb_init() term_width = term.width term_height = term.height inp = -1 dirty = True while True: if dirty or session.poll_event('refresh'): lb_refresh(lb) inp = getch(1) dirty = True # terminal dimensions may change, so we adapt to that if (term_width != term.width or term_height != term.height): echo(term.clear) show_banner() lb = lb_init() lb_refresh(lb) term_width = term.width term_height = term.height if inp is not None: echo(lb.process_keystroke(inp)) if lb.selected and lb.selection[0] is not None: script = key_map.get(lb.selection[0]) if script: if script == u'x': goto('main') elif script == u'v' and 'sysop' in session.user.groups: gosub('ttyplay') else: echo(term.clear) gosub(script) echo(term.clear) show_banner() else: handled = False try: for option in ini.CFG.options('sesame'): if option.endswith('_key'): door = option.replace('_key', '') key = ini.CFG.get('sesame', option) if inp == key: gosub('sesame', door) handled = True break except ConfigError: pass if not handled: dirty = False
def main(host, port=None, encoding='cp437'): """ Call script with argument host and optional argument port to connect to a telnet server. ctrl-^ to disconnect. """ # pylint: disable=R0914,R0912,R0915 # Too many local variables # Too many branches # Too many statements import telnetlib from functools import partial from x84.bbs import getsession, getterminal, echo, getch, from_cp437, telnet import logging log = logging.getLogger() assert encoding in ('utf8', 'cp437') session, term = getsession(), getterminal() session.activity = 'connecting to %s' % (host,) port = int(port) if port is not None else 23 telnet_client = telnetlib.Telnet() telnet_client.set_option_negotiation_callback(partial( telnet.callback_cmdopt, env_term=session.env['TERM'], height=term.height, width=term.width)) echo(u"\r\n\r\nEscape character is 'ctrl-^.'") if not session.user.get('expert', False): getch(3) echo(u'\r\nTrying %s:%s... ' % (host, port,)) # pylint: disable=W0703 # Catching too general exception Exception try: telnet_client.open(host, port) except Exception as err: echo(term.bold_red('\r\n%s\r\n' % (err,))) echo(u'\r\n press any key ..') getch() return echo(u'\r\n... ') inp = session.read_event('input', timeout=0) echo(u'\r\nConnected to %s.' % (host,)) session.activity = 'connected to %s' % (host,) carriage_returned = False with term.fullscreen(): while True: if encoding == 'cp437': try: unistring = from_cp437( telnet_client.read_very_eager().decode('iso8859-1')) except EOFError: break else: unistring = telnet_client.read_very_eager().decode('utf8') if 0 != len(unistring): echo(unistring) if inp is not None: if inp == chr(30): # ctrl-^ telnet_client.close() echo(u'\r\n' + term.clear_el + term.normal) break elif not carriage_returned and inp in (b'\x0d', b'\x0a'): telnet_client.write(b'\x0d') log.debug('send {!r}'.format(b'\x0d')) carriage_returned = True elif carriage_returned and inp in (b'\x0a', b'\x00'): carriage_returned = False elif inp is not None: telnet_client.write(inp) log.debug('send {!r}'.format(inp)) carriage_returned = False inp = session.read_event('input', timeout=KEY_POLL) echo(u'\r\nConnection closed.\r\n') echo(u''.join(('\r\n\r\n', term.clear_el, term.normal, 'press any key'))) echo(u'\x1b[r') # unset 'set scrolling region', sometimes set by BBS's session.flush_event('input') getch() return
def post_bbs_scene(oneliner, dumb=True): """ Prompt for posting to bbs-scene.org oneliners API, returning thread if posting occured. """ # pylint: disable=R0914 # Too many local variables import logging import xml.etree.ElementTree import requests from x84.bbs import echo, getch, getterminal, getsession, ini logger = logging.getLogger() session, term = getsession(), getterminal() prompt_api = u'MAkE AN ASS Of YOURSElf ON bbS-SCENE.ORG?!' heard_api = u'YOUR MESSAGE hAS bEEN brOAdCAStEd.' yloc = term.height - 3 if dumb: # post to bbs-scene.org ? echo('\r\n\r\n' + term.bold_blue(prompt_api) + u' [yn]') inp = getch(1) while inp not in (u'y', u'Y', u'n', u'N'): inp = getch() if inp in (u'n', u'N'): # no? then just post locally add_oneline(oneliner.strip()) return None else: # fancy prompt, 'post to bbs-scene.org?' sel = get_selector() sel.colors['selected'] = term.red_reverse echo(term.move(sel.yloc - 1, sel.xloc) or ('\r\n\r\n')) echo(term.blue_reverse(prompt_api.center(sel.width))) echo(sel.refresh()) while not sel.selected and not sel.quit: echo(sel.process_keystroke(getch())) session.buffer_event('refresh', 'dirty') if sel.quit or sel.selection == sel.right: echo(term.normal + term.move(yloc, 0) + term.clear_eol) echo(term.move(sel.yloc, 0) + term.clear_eol) return None # This is an AJAX effect. # Dispatch a thread to fetch updates, whose callback # will cause the database and pager to update. url = 'http://bbs-scene.org/api/onelinerz.xml' usernm = ini.CFG.get('bbs-scene', 'user') passwd = ini.CFG.get('bbs-scene', 'pass') data = { 'bbsname': ini.CFG.get('system', 'bbsname'), 'alias': session.user.handle, 'oneliner': oneliner.strip(), } # post to bbs-scene.rog req = requests.post(url, auth=(usernm, passwd), data=data) if (req.status_code != 200 or (xml.etree.ElementTree.XML( req.content).find('success').text != 'true')): echo(u'\r\n\r\n%srequest failed,\r\n' % (term.clear_eol, )) echo(u'%r' % (req.content, )) echo(u'\r\n\r\n%s(code: %s).\r\n' % ( term.clear_eol, req.status_code, )) echo(u'\r\n%sPress any key ..' % (term.clear_eol, )) logger.warn('bbs-scene.org api request failed') getch() return None logger.info('bbs-scene.org api (%d): %r/%r', req.status_code, session.user.handle, oneliner.strip()) thread = FetchUpdates() thread.start() if not dumb: # clear line w/input bar, echo(term.normal + term.move(yloc, 0) + term.clear_eol) # clear line w/selector echo(term.move(sel.yloc, 0) + term.clear_eol) else: echo('\r\n\r\n' + heard_api) getch(2) return thread
def browse_dir(session, db_desc, term, lightbar, directory, sub=False): """ Browse a directory. """ # build and sort directory listing reload_dir(session, directory, lightbar, sub) echo(lightbar.refresh()) filename, _ = lightbar.selection browser.last_diz_len = 0 diz = '' # force it to describe the very first file when browser loads inp = lightbar.keyset['home'][0] # prime our loop isdir = False filepath = '' while True: # read from lightbar while not inp: inp = getch(0.2) # respond to screen dimension change by redrawing if session.poll_event('refresh'): draw_interface(term, lightbar) describe_file(term, diz=diz, directory=directory, filename=filename, isdir=isdir) idx = lightbar.vitem_idx shift = lightbar.vitem_shift # pass input to lightbar lightbar.process_keystroke(inp) filename, _ = lightbar.selection filepath = os.path.join(directory, filename) relativename = filepath[len(ROOT):] isdir = bool(filepath[-1:] == os.path.sep) _, ext = os.path.splitext(filename.lower()) if inp in lightbar.keyset['home']: # lightbar 'home' keystroke bug; redraw current line echo(lightbar.refresh_row(idx)) echo(lightbar.refresh_row(lightbar.vitem_idx)) elif lightbar.quit: # 'exit' key pressed return False elif inp in (u' ',) and filename[-1:] != os.path.sep: # 'flag' key pressed; don't allow flagging directories if filepath in browser.flagged_files: # already flagged; un-flag browser.flagged_files.remove(filepath) else: browser.flagged_files.add(filepath) session.user['flaggedfiles'] = browser.flagged_files reload_dir(session, directory, lightbar, sub) if is_flagged_dir(directory): echo(lightbar.refresh()) else: echo(lightbar.refresh_row(lightbar.vitem_idx)) lightbar.move_down() elif inp in (u'-',): # 'unflag all' pressed session.user['flaggedfiles'] = browser.flagged_files = set() reload_dir(session, directory, lightbar, sub) echo(lightbar.refresh()) elif inp in (u'd',) and len(browser.flagged_files): download_files(term, session) reload_dir(session, directory, lightbar, sub) draw_interface(term, lightbar) elif inp in (u'u',): upload_files(term) reload_dir(session, directory, lightbar, sub) draw_interface(term, lightbar) elif inp in (u'e',) and session.user.is_sysop and not isdir: edit_description(relativename, db_desc) reload_dir(session, directory, lightbar, sub) draw_interface(term, lightbar) clear_diz(term) save_diz = True if lightbar.selected or inp in (term.KEY_LEFT, term.KEY_RIGHT,): if sub and inp is term.KEY_LEFT: # term.KEY_LEFT backs up return True if (isdir or is_flagged_dir(filename) and ( lightbar.selected or inp is term.KEY_RIGHT)): # 'select' key pressed if filename == '..{0}'.format(os.path.sep): # is directory and is a parent directory; back out return True # RECURSION if not browse_dir(session, db_desc, term, lightbar, filepath, True): # sub directory; jump in return False reload_dir(session, directory, lightbar, sub) lightbar.vitem_shift = shift lightbar.vitem_idx = idx echo(lightbar.refresh()) if relativename in db_desc: save_diz = False diz = db_desc[relativename] if ext in COLLY_EXTENSIONS: decoder = 'cp437_art' if session.encoding == 'utf8': decoder = COLLY_DECODING try: diz = [line.decode(decoder, errors='replace') for line in diz] except UnicodeEncodeError: diz = [u'Invalid characters in FILE_ID.DIZ'] elif ext in browser.diz_extractors: # is (supported) archive diz = browser.diz_extractors[ext](filepath).splitlines() elif ext in COLLY_EXTENSIONS: # is ASCII colly, pull diz from between markers if available. diz = get_diz_from_colly(filepath=filepath) or diz # save diz in raw format, but display decoded save_diz = False db_desc[relativename] = diz decoder = 'cp437_art' if session.encoding == 'utf8': decoder = COLLY_DECODING try: diz = [line.decode(decoder, errors='replace') for line in diz] except UnicodeEncodeError: diz = [u'Invalid characters in FILE_ID.DIZ'] db_desc[relativename] = diz elif is_flagged_dir(filename): # is pseudo-folder for flagged files save_diz = False diz = get_instructions(term, session.user.is_sysop) elif isdir: # is directory; don't give it a description save_diz = False diz = [] else: # is normal file save_diz = False diz = [u'No description'] if not UPLOADS_DIR.find(directory) and save_diz: # write description to diz db when save_diz is True with db_desc: db_desc[relativename] = diz browser.last_diz_len = len(diz) describe_file(term=term, diz=diz, directory=directory, filename=filename, isdir=isdir) echo(lightbar.refresh_quick() + lightbar.fixate()) inp = None
def process_keystroke(inp, user): """ Process keystroke, ``inp``, for target ``user``. """ # pylint: disable=R0914,R0912,R0915,R0911,W0603 # Too many local variables # Too many branches # Too many statements # Too many return statements # Using the global statement # ^ lol, this is one of those things that should be # refactored into smaller subroutines =) from x84.bbs import getsession, getterminal, echo, getch, gosub from x84.bbs import LineEditor, Ansi from x84.default.nua import set_email, set_location from x84.bbs.ini import CFG def_timeout = CFG.getint('system', 'timeout') global EXIT session, term = getsession(), getterminal() is_self = bool(user.handle == session.user.handle) invalid = u'\r\niNVAlid.' assert is_self or 'sysop' in session.user.groups if is_self and inp in (u'^',): user['sausercookie'] = u'' user['sapasscookie'] = u'' if is_self and inp in (u'c', u'C'): gosub('charset') elif is_self and inp in (u'@',): echo(u'\r\neNTER SA \'pass\' cookie: ') sapasscookie = LineEditor(50, session.user['sapasscookie']).read() echo(u"\r\n\r\nset SA pass cookie to '%s'? [yn]" % (sapasscookie,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): session.user['sapasscookie'] = sapasscookie break elif inp2 in (u'n', u'N'): break elif is_self and inp in (u'!',): echo(u'\r\neNTER SA \'user\' cookie: ') sausercookie = LineEditor(30, session.user['sausercookie']).read() echo(u"\r\n\r\nset SA user cookie to '%s'? [yn]" % (sausercookie,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): session.user['sausercookie'] = sausercookie break elif inp2 in (u'n', u'N'): break elif is_self and inp in (u't', u'T'): echo(term.move(term.height - 1, 0)) echo(ABOUT_TERM + u'\r\n') echo(u'\r\ntERMiNAl tYPE: ') term = LineEditor(30, session.env.get('TERM')).read() echo(u"\r\n\r\nset TERM to '%s'? [yn]" % (term,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): session.env['TERM'] = term break elif inp2 in (u'n', u'N'): break elif is_self and inp in (u'w', u'W'): echo(u'\r\ntERMiNAl Width: ') width = LineEditor(3, str(term.width)).read() try: width = int(width) except ValueError: echo(invalid) return True if width < 0 or width > 999: echo(invalid) return True echo(u"\r\n\r\nset COLUMNS=%d? [yn]" % (width,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): term.columns = width break elif inp2 in (u'n', u'N'): break elif is_self and inp in (u'h', u'H'): echo(u'\r\ntERMiNAl hEiGht: ') height = LineEditor(3, str(term.height)).read() try: height = int(height) except ValueError: echo(invalid) return True if height < 0 or height > 999: echo(invalid) return True echo(u"\r\n\r\nset LINES=%d? [yn]" % (height,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): term.rows = height break elif inp2 in (u'n', u'N'): break elif 'sysop' in session.user.groups and inp in (u'd', u'D',): echo(u"\r\n\r\ndElEtE %s ? [yn]" % (user.handle,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user.delete() break elif inp2 in (u'n', u'N'): break EXIT = True elif 'sysop' in session.user.groups and inp in (u's', u'S',): sysop = not 'sysop' in user.groups echo(u"\r\n\r\n%s SYSOP ACCESS? [yn]" % ( 'ENAblE' if sysop else 'diSAblE',)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): if sysop: user.groups.add('sysop') else: user.groups.remove('sysop') user.save() break elif inp2 in (u'n', u'N'): break elif inp in (u'p', u'P'): from x84.default.nua import set_password set_password(user) echo(u"\r\n\r\nSEt PASSWORd ? [yn]") while True: inp2 = getch() if inp2 in (u'y', u'Y'): user.save() break elif inp2 in (u'n', u'N'): break elif inp in (u'.',): echo(term.move(0, 0) + term.normal + term.clear) echo(term.move(int(term.height * .8), 0)) for line in Ansi(ABOUT_DOT_PLAN).wrap( term.width / 3).splitlines(): echo(line.center(term.width).rstrip() + u'\r\n') echo(u'\r\n\r\nPRESS ANY kEY ...') getch() if is_self: gosub('editor', '.plan') else: tmpkey = '%s-%s' % (user.handle, user.plan) draft = user.get('.plan', u'') session.user[tmpkey] = draft gosub('editor', tmpkey) if session.user.get(tmpkey, u'') != draft: echo(u"\r\n\r\nSEt .PlAN ? [yn]") while True: inp2 = getch() if inp2 in (u'y', u'Y'): user['.plan'] = session.user[tmpkey] break elif inp2 in (u'n', u'N'): break elif inp in (u'l', u'L'): echo(term.move(term.height - 1, 0)) set_location(user) echo(u"\r\n\r\nSEt lOCAtiON tO '%s'? [yn]" % (user.location,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user.save() break elif inp2 in (u'n', u'N'): break elif inp in (u'e', u'E'): echo(term.move(term.height - 1, 0)) set_email(user) echo(u"\r\n\r\nSEt EMAil tO '%s'? [yn]" % (user.email,)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user.save() break elif inp2 in (u'n', u'N'): break elif inp in (u'i', u'I'): echo(u'\r\ntiMEOUt (0=NONE): ') timeout = LineEditor(6, str(user.get('timeout', def_timeout))).read() try: timeout = int(timeout) except ValueError: echo(invalid) return True if timeout < 0: echo(invalid) return True echo(u"\r\n\r\nSet tiMEOUt=%s? [yn]" % ( timeout if timeout else 'None',)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user['timeout'] = timeout session.send_event('set-timeout', timeout) break elif inp2 in (u'n', u'N'): break elif inp in (u'm', u'M'): mesg = False if user.get('mesg', True) else True echo(u"\r\n\r\n%s iNStANt MESSAGiNG? [yn]" % ( 'ENAblE' if mesg else 'DiSAblE',)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user['mesg'] = mesg break elif inp2 in (u'n', u'N'): break elif inp in (u'x', u'X'): expert = not user.get('expert', False) echo(u"\r\n\r\n%s EXPERt MOdE? [yn]" % ( 'ENAblE' if expert else 'DiSAblE',)) while True: inp2 = getch() if inp2 in (u'y', u'Y'): user['expert'] = expert break elif inp2 in (u'n', u'N'): break elif inp in (u'q', u'Q',): EXIT = True else: return False return True
def main(): """ Main procedure. """ # pylint: disable=R0912 # Too many branches from x84.bbs import getsession, getterminal, ini, echo, getch session, term = getsession(), getterminal() pager, selector = get_pager(), get_selector() thread = None if ini.CFG.has_section('bbs-scene'): thread = FetchUpdates() thread.start() session.activity = u'one-liners [bbs-scene.org]' else: session.activity = u'one-liners' # flag a pager update, dirty = True # force screen clear on first loop, session.buffer_event('refresh', ('init', )) while True: # 1. calculate and redraw screen, # or enter dumb pager mode (no scrolling) if session.poll_event('refresh'): pager, selector = get_pager(), get_selector(selector.selection) echo(banner()) dirty = True if chk_thread(thread): thread = None while session.read_event('oneliner_update', 0.15): dirty = True if dirty and (session.env.get('TERM') != 'unknown' and not session.user.get('expert', False) and term.width >= 78 and term.height >= 20): # smart terminal redraw(pager, selector) dirty = False elif dirty: # dumb terminal if thread is not None: wait_for(thread) if chk_thread(thread): thread = None echo(u'\r\n\r\n') return dummy_pager() # 2. detect and process keyboard input, inp = getch(1) if inp is not None: # input is multiplexed to both interfaces echo(pager.process_keystroke(inp)) echo(selector.process_keystroke(inp)) # selected 'yes' & return, 'say something' if (selector.selected and selector.selection == selector.left): # re-assign thread so that it is checked for updates thread = saysomething(dumb=False) # undo 'selected' state of yes/no bar, selector.selected = False # quit 'q', or selected 'no' & return elif (selector.selected and selector.selection == selector.right or pager.quit): return
def nothing(): """ Do nothing. """ from x84.bbs import echo, getch echo(u'Nothing to do.') getch(3)
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
def main(save_key=u'draft'): """ Main procedure. """ # pylint: disable=R0914,R0912,R0915 # Too many local variables # Too many branches # Too many statements from x84.bbs import getsession, getterminal, echo, getch, Ansi, Pager session, term = getsession(), getterminal() movement = (term.KEY_UP, term.KEY_DOWN, term.KEY_NPAGE, term.KEY_PPAGE, term.KEY_HOME, term.KEY_END, u'\r', term.KEY_ENTER) keyset = { 'edit': (term.KEY_ENTER, ), 'command': (unichr(27), term.KEY_ESCAPE), 'kill': (u'K', ), 'undo': ( u'u', 'U', ), 'goto': (u'G', ), 'insert': (u'I', ), 'insert-before': (u'O', ), 'insert-after': (u'o', ), 'join': (u'J', ), 'rubout': ( unichr(8), unichr(127), unichr(23), term.KEY_BACKSPACE, ), } def merge(): """ Merges line editor content as a replacement for the currently selected lightbar row. Returns True if text inserted caused additional rows to be appended, which is meaningful in a window refresh context. """ lightbar.content[lightbar.index] = [ lightbar.selection[0], softwrap_join(wrap_rstrip(lneditor.content)) + HARDWRAP ] prior_length = len(lightbar.content) prior_position = lightbar.position set_lbcontent(lightbar, get_lbcontent(lightbar)) if len(lightbar.content) - prior_length == 0: echo(lightbar.refresh_row(prior_position[0])) return False while len(lightbar.content) - prior_length > 0: # hidden move-down for each appended line lightbar.move_down() prior_length += 1 return True def statusline(lightbar): """ Display status line and command help on ``lightbar`` borders """ lightbar.colors['border'] = term.red if edit else term.yellow keyset_cmd = u'' if not edit: keyset_cmd = u''.join(( term.yellow(u'-( '), term.yellow_underline(u'S'), u':', term.bold(u'ave'), u' ', term.yellow_underline(u'A'), u':', term.bold(u'bort'), u' ', term.yellow_underline(u'?'), u':', term.bold(u'help'), term.yellow(u' )-'), )) keyset_cmd = lightbar.pos( lightbar.height - 1, max(0, lightbar.width - (len(Ansi(keyset_cmd)) + 3))) + keyset_cmd return u''.join(( lightbar.border(), keyset_cmd, lightbar.pos(lightbar.height, lightbar.xpadding), u''.join(( term.red(u'-[ '), u'EditiNG liNE ', term.reverse_red('%d' % (lightbar.index + 1, )), term.red(u' ]-'), )) if edit else u''.join(( term.yellow(u'-( '), u'liNE ', term.yellow('%d/%d ' % ( lightbar.index + 1, len(lightbar.content), )), '%d%% ' % (int((float(lightbar.index + 1) / max(1, len(lightbar.content))) * 100)), term.yellow(u' )-'), )), lightbar.title(u''.join(( term.red('-] '), term.bold(u'Escape'), u':', term.bold_red(u'command mode'), term.red(' [-'), )) if edit else u''.join(( term.yellow('-( '), term.bold(u'Enter'), u':', term.bold_yellow(u'edit mode'), term.yellow(' )-'), ))), )) def redraw_lneditor(lightbar, lneditor): """ Return ucs suitable for refreshing line editor. """ return ''.join((term.normal, statusline(lightbar), lneditor.border(), lneditor.refresh())) def get_ui(ucs, lightbar=None): """ Returns Lightbar and ScrollingEditor instance. """ lbr = get_lightbar(ucs) lbr.position = (lightbar.position if lightbar is not None else (0, 0)) lne = get_lneditor(lbr) return lbr, lne def banner(): """ Returns string suitable clearing screen """ return u''.join((term.move(0, 0), term.normal, term.clear)) def redraw(lightbar, lneditor): """ Returns ucs suitable for redrawing Lightbar and ScrollingEditor UI elements. """ return u''.join(( term.normal, redraw_lightbar(lightbar), redraw_lneditor(lightbar, lneditor) if edit else u'', )) def redraw_lightbar(lightbar): """ Returns ucs suitable for redrawing Lightbar. """ return u''.join(( statusline(lightbar), lightbar.refresh(), )) def resize(lightbar): """ Resize Lightbar. """ if edit: # always re-merge current line on resize, merge() lbr, lne = get_ui(get_lbcontent(lightbar), lightbar) echo(redraw(lbr, lne)) return lbr, lne ucs = session.user.get(save_key, u'') lightbar, lneditor = get_ui(ucs, None) echo(banner()) dirty = True edit = False digbuf, num_repeat = u'', -1 count_repeat = lambda: range(num_repeat if num_repeat != -1 else 1) while True: # poll for refresh if session.poll_event('refresh'): echo(banner()) lightbar, lneditor = resize(lightbar) dirty = True if dirty: session.activity = 'editing %s' % (save_key, ) echo(redraw(lightbar, lneditor)) dirty = False # poll for input inp = getch(1) # buffer keystrokes for repeat if (not edit and inp is not None and type(inp) is not int and inp.isdigit()): digbuf += inp if len(digbuf) > 10: # overflow, echo(u'\a') digbuf = inp try: num_repeat = int(digbuf) except ValueError: try: num_repeat = int(inp) except ValueError: pass continue else: digbuf = u'' # toggle edit mode, if inp in keyset['command'] or not edit and inp in keyset['edit']: edit = not edit # toggle if not edit: # switched to command mode, merge our lines echo(term.normal + lneditor.erase_border()) merge() lightbar.colors['highlight'] = term.yellow_reverse else: # switched to edit mode, save draft, # instantiate new line editor save_draft(save_key, get_lbcontent(lightbar)) lneditor = get_lneditor(lightbar) lightbar.colors['highlight'] = term.red_reverse dirty = True # command mode, kill line elif not edit and inp in keyset['kill']: # when 'killing' a line, make accomidations to clear # bottom-most row, otherwise a ghosting effect occurs for _count in count_repeat(): del lightbar.content[lightbar.index] set_lbcontent(lightbar, get_lbcontent(lightbar)) if lightbar.visible_bottom > len(lightbar.content): echo(lightbar.refresh_row(lightbar.visible_bottom + 1)) else: dirty = True save_draft(save_key, get_lbcontent(lightbar)) # command mode, insert line elif not edit and inp in keyset['insert']: for _count in count_repeat(): lightbar.content.insert(lightbar.index, ( lightbar.index, HARDWRAP, )) set_lbcontent(lightbar, get_lbcontent(lightbar)) save_draft(save_key, get_lbcontent(lightbar)) dirty = True # command mode; goto line elif not edit and inp in keyset['goto']: if num_repeat == -1: # 'G' alone goes to end of file, num_repeat = len(lightbar.content) echo(lightbar.goto((num_repeat or 1) - 1)) echo(statusline(lightbar)) # command mode; insert-before (switch to edit mode) elif not edit and inp in keyset['insert-before']: lightbar.content.insert(lightbar.index, ( lightbar.index, HARDWRAP, )) set_lbcontent(lightbar, get_lbcontent(lightbar)) edit = dirty = True # switched to edit mode, save draft, # instantiate new line editor lightbar.colors['highlight'] = term.red_reverse lneditor = get_lneditor(lightbar) save_draft(save_key, get_lbcontent(lightbar)) # command mode; insert-after (switch to edit mode) elif not edit and inp in keyset['insert-after']: lightbar.content.insert(lightbar.index + 1, ( lightbar.index + 1, HARDWRAP, )) set_lbcontent(lightbar, get_lbcontent(lightbar)) edit = dirty = True # switched to edit mode, save draft, # instantiate new line editor lightbar.colors['highlight'] = term.red_reverse lightbar.move_down() lneditor = get_lneditor(lightbar) save_draft(save_key, get_lbcontent(lightbar)) # command mode, undo elif not edit and inp in keyset['undo']: for _count in count_repeat(): if len(UNDO): set_lbcontent(lightbar, UNDO.pop()) dirty = True else: echo(u'\a') break # command mode, join line elif not edit and inp in keyset['join']: for _count in count_repeat(): if lightbar.index + 1 < len(lightbar.content): idx = lightbar.index lightbar.content[idx] = ( idx, WHITESPACE.join(( lightbar.content[idx][1].rstrip(), lightbar.content[idx + 1][1].lstrip(), ))) del lightbar.content[idx + 1] prior_length = len(lightbar.content) set_lbcontent(lightbar, get_lbcontent(lightbar)) if len(lightbar.content) - prior_length > 0: lightbar.move_down() dirty = True else: echo(u'\a') break if dirty: save_draft(save_key, get_lbcontent(lightbar)) # command mode, basic cmds & movement elif not edit and inp is not None: if inp in ( u'a', u'A', ): if yes_no( lightbar, term.yellow(u'- ') + term.bold_red(u'AbORt') + term.yellow(u' -')): return False dirty = True elif inp in ( u's', u'S', ): if yes_no( lightbar, term.yellow(u'- ') + term.bold_green(u'SAVE') + term.yellow(u' -'), term.reverse_green): save(save_key, get_lbcontent(lightbar)) return True dirty = True elif inp in (u'?', ): pager = Pager(lightbar.height, lightbar.width, lightbar.yloc, lightbar.xloc) pager.update(get_help()) pager.colors['border'] = term.bold_blue echo(pager.border() + pager.title(u''.join(( term.bold_blue(u'-( '), term.white_on_blue(u'r'), u':', term.bold(u'eturn'), u' ', term.bold_blue(u' )-'), )))) pager.keyset['exit'].extend([u'r', u'R']) pager.read() echo(pager.erase_border()) dirty = True else: moved = False for _count in count_repeat(): echo(lightbar.process_keystroke(inp)) moved = lightbar.moved or moved if moved: echo(statusline(lightbar)) # edit mode; movement elif edit and inp in movement: dirty = merge() if inp in ( u'\r', term.KEY_ENTER, ): lightbar.content.insert(lightbar.index + 1, [lightbar.selection[0] + 1, u'']) inp = term.KEY_DOWN dirty = True ucs = lightbar.process_keystroke(inp) if lightbar.moved: echo(term.normal + lneditor.erase_border()) echo(ucs) lneditor = get_lneditor(lightbar) save_draft(save_key, get_lbcontent(lightbar)) echo(lneditor.refresh()) else: dirty = True # edit mode -- append character / backspace elif edit and inp is not None: if (inp in keyset['rubout'] and len(lneditor.content) == 0 and lightbar.index > 0): # erase past margin, echo(term.normal + lneditor.erase_border()) del lightbar.content[lightbar.index] lightbar.move_up() set_lbcontent(lightbar, get_lbcontent(lightbar)) lneditor = get_lneditor(lightbar) dirty = True else: # edit mode, add/delete ch echo(lneditor.process_keystroke(inp)) if lneditor.moved: echo(statusline(lightbar)) if inp is not None and type(inp) is not int and not inp.isdigit(): # commands were processed, reset num_repeat to 1 num_repeat = -1
def main(): """ Main procedure. """ # pylint: disable=R0914,W0141,R0912 # Too many local variables # Used builtin function 'map' # Too many branches from x84.bbs import getsession, getterminal, echo, getch, syncterm_setfont from x84.engine import __url__ as url import platform import random import sys import os session, term = getsession(), getterminal() session.activity = 'System Info' artfile = os.path.join( os.path.dirname(__file__), 'art', 'plant.ans', ) # pylint: disable=W0633 # Attempting to unpack a non-sequence defined at line 1160 of # platform system, _, release, _, machine, _ = platform.uname() body = [ u'authors:', u'Johannes Lundberg', u'Jeffrey Quast', u'Wijnand Modderman-Lenstra', u'', u'artwork:', u'hellbeard!impure', u'\r\n', u'system: %s %s %s' % (system, release, machine), u'software: x/84', url, u'\r\n', (platform.python_implementation() + u' ' + '-'.join(map(str, sys.version_info[3:]))) + u' ' + (platform.python_version() if hasattr(platform, 'python_implementation') else u'.'.join( map(str, sys.version_info[:3]))), ] melt_colors = ([term.normal] + [term.bold_blue] * 3 + [term.red] * 4 + [term.bold_red] + [term.bold_white] + [term.normal] * 6 + [term.blue] * 2 + [term.bold_blue] + [term.bold_white] + [term.normal]) art = open(artfile).read().decode('cp437_art') \ if os.path.exists(artfile) else u'' otxt = list(art.splitlines()) for num, line in enumerate(body): while num > len(otxt): otxt += [ u'', ] otxt[num] = otxt[num][:int(term.width / 2.5)] + u' ' + line width = max([term.length(line) for line in otxt]) height = len(otxt) num_stars = int((term.width * term.height) * .005) stars = dict([(n, (random.choice('\\|/-'), float(random.choice(range(term.width))), float(random.choice(range(term.height))))) for n in range(num_stars)]) melting = {} show_star = False tm_out, tm_min, tm_max, tm_step = 0.08, 0.01, 2.0, .01 wind = (0.7, 0.1, 0.01, 0.01) def refresh(): """ Refresh screen and return top-left (x, y) location. """ # set syncterm font to cp437 if term.kind.startswith('ansi'): echo(syncterm_setfont('cp437')) echo(u'\r\n\r\n') if term.width < width: echo(u''.join(( term.move(term.height, 0), u'\r\n\r\n', term.bold_red + 'screen too thin! (%s/%s)' % ( term.width, width, ), u'\r\n\r\n', u'press any key...', ))) getch() return (None, None) if term.height < height: echo(u''.join(( term.move(term.height, 0), u'\r\n\r\n', term.bold_red + 'screen too short! (%s/%s)' % (term.height, height), u'\r\n\r\n', u'press any key...', ))) getch() return (None, None) xloc = (term.width / 2) - (width / 2) yloc = (term.height / 2) - (height / 2) echo(u''.join(( term.normal, (u'\r\n' + term.clear_eol) * term.height, u''.join([ term.move(yloc + abs_y, xloc) + line for abs_y, line in enumerate(otxt) ]), ))) return xloc, yloc txt_x, txt_y = refresh() if (txt_x, txt_y) == (None, None): return def char_at_pos(yloc, xloc, txt_y, txt_x): """ Return art (y, x) for location """ return (u' ' if yloc - txt_y < 0 or yloc - txt_y >= height or xloc - txt_x < 0 or xloc - txt_x >= len(otxt[yloc - txt_y]) else otxt[yloc - txt_y][xloc - txt_x]) def iter_wind(xslope, yslope, xdir, ydir): """ An easterly Wind """ xslope += xdir yslope += ydir if xslope <= 0.5: xdir = random.choice([0.01, 0.015, 0.02]) elif xslope >= 1: xdir = random.choice([-0.01, -0.015, -0.02]) if yslope <= -0.1: ydir = random.choice([0.01, 0.015, 0.02, 0.02]) elif yslope >= 0.1: ydir = random.choice([-0.01, -0.015, -0.02]) return xslope, yslope, xdir, ydir def iter_star(char, xloc, yloc): """ Given char and current position, apply wind and return new char and new position. """ if char == '\\': char = '|' elif char == '|': char = '/' elif char == '/': char = '-' elif char == '-': char = '\\' xloc += wind[0] yloc += wind[1] if xloc < 1 or xloc > term.width: xloc = (1.0 if xloc > term.width else float(term.width)) yloc = (float(random.choice(range(term.height)))) if yloc < 1 or yloc > term.height: yloc = (1.0 if yloc > term.height else float(term.height)) xloc = (float(random.choice(range(term.width)))) return char, xloc, yloc def erase(star_idx): """ erase old star before moving .. """ if show_star: _, xloc, yloc = stars[star_idx] echo(u''.join(( term.move(int(yloc), int(xloc)), term.normal, char_at_pos(int(yloc), int(xloc), txt_y, txt_x), ))) def melt(): """ Iterate through all stars and phase through melt sequence. """ def melted(yloc, xloc): """ shift melt, delete if disappeared. """ melting[(yloc, xloc)] -= 1 if 0 == melting[(yloc, xloc)]: del melting[(yloc, xloc)] for (yloc, xloc), phase in melting.items(): echo(u''.join(( term.move(yloc, xloc), melt_colors[phase - 1], char_at_pos(yloc, xloc, txt_y, txt_x), ))) melted(yloc, xloc) def draw_star(star, xloc, yloc): """ draw star a (x, y) location """ char = char_at_pos(int(yloc), int(xloc), txt_y, txt_x) if char != ' ': melting[(int(yloc), int(xloc))] = len(melt_colors) if show_star: echo(term.move(int(yloc), int(xloc)) + melt_colors[-1] + star) with term.hidden_cursor(): while txt_x is not None and txt_y is not None: if session.poll_event('refresh'): num_stars = int(num_stars) stars = dict([(n, (random.choice('\\|/-'), float(random.choice(range(term.width))), float(random.choice(range(term.height))))) for n in range(num_stars)]) otxt = list(art.splitlines()) for num, line in enumerate(body): while num > len(otxt): otxt += [ u'', ] otxt[num] = (otxt[num][:int(term.width / 2.5)] + u' ' + line) txt_x, txt_y = refresh() continue inp = getch(tm_out) if inp in (term.KEY_UP, 'k'): if tm_out >= tm_min: tm_out -= tm_step elif inp in (term.KEY_DOWN, 'j'): if tm_out <= tm_max: tm_out += tm_step elif inp in (term.KEY_LEFT, 'h'): if num_stars > 2: num_stars = int(num_stars * .5) stars = dict([(n, (random.choice('\\|/-'), float(random.choice(range(term.width))), float(random.choice(range(term.height))))) for n in range(num_stars)]) elif inp in (term.KEY_RIGHT, 'l'): if num_stars < (term.width * term.height) / 4: num_stars = int(num_stars * 1.5) stars = dict([(n, (random.choice('\\|/-'), float(random.choice(range(term.width))), float(random.choice(range(term.height))))) for n in range(num_stars)]) elif inp in (u'*', ) and not show_star: show_star = True elif inp in (u'*', ) and show_star: for star in stars: erase(star) show_star = False elif inp is not None: echo(term.move(term.height, 0)) break melt() for star_key, star_val in stars.items(): erase(star_key) # pylint: disable=W0142 # Used * or ** magic stars[star_key] = iter_star(*star_val) draw_star(*stars[star_key]) # pylint: disable=W0142 # Used * or ** magic wind = iter_wind(*wind)
def main(): """ Main procedure. """ # pylint: disable=R0914,R0912 # Too many local variables # Too many branches from x84.bbs import DBProxy, getsession, getterminal, echo from x84.bbs import ini, LineEditor, timeago, Ansi, showcp437 from x84.bbs import disconnect, getch import time import os session, term = getsession(), getterminal() session.activity = 'logging off' handle = session.handle if (session.handle is not None) else 'anonymous' max_user = ini.CFG.getint('nua', 'max_user') prompt_msg = u'[spnG]: ' if session.user.get('expert', False) else ( u'%s:AY SOMEthiNG %s:REViOUS %s:EXt %s:Et thE f**k Off !\b' % ( term.bold_blue_underline(u's'), term.blue_underline(u'p'), term.blue_underline(u'n'), term.red_underline(u'Escape/g'), )) prompt_say = u''.join(( term.bold_blue(handle), term.blue(u' SAYS WhAt'), term.bold(': '), )) boards = ( ( '1984.ws', 'x/84 dEfAUlt bOARd', 'dingo', ), ( 'htc.zapto.org', 'Haunting the Chapel', 'Mercyful', ), ( 'pharcyde.ath.cx', 'Pharcyde BBS', 'Access Denied', ), ( 'bloodisland.ph4.se', 'Blood Island', 'xzip', ), ( 'ssl.archaicbinary.net', 'Archaic Binary', 'Wayne Smith', ), ( 'bbs.godta.com', 'godta', 'sk-5', ), ) board_fmt = u'%25s %-30s %-15s\r\n' goodbye_msg = u''.join(( term.move(term.height, 0), u'\r\n' * 10, u'tRY ANOthER fiNE bOARd', term.bold(u':'), u'\r\n\r\n', board_fmt % ( term.underline('host'.rjust(25)), term.underline('board'.ljust(30)), term.underline('sysop'.ljust(15)), ), u'\r\n'.join([ board_fmt % ( term.bold(host.rjust(25)), term.reverse(board.center(30)), term.bold_underline(sysop), ) for (host, board, sysop) in boards ]), u'\r\n\r\n', term.bold(u'back to the mundane world...'), u'\r\n', )) commit_msg = term.bold_blue( u'-- ! thANk YOU fOR YOUR CONtRibUtiON, bROthER ! --') write_msg = term.red_reverse(u'bURNiNG tO ROM, PlEASE WAiT ...') db_firstrecord = ((time.time() - 1984, u'B. b.', u'bEhAVE YOURSElVES ...'), ) automsg_len = 40 artfile = os.path.join(os.path.dirname(__file__), 'art', '1984.asc') def refresh_prompt(msg): """ Refresh automsg prompt using string msg. """ echo(u''.join((u'\r\n\r\n', term.clear_eol, msg))) def refresh_automsg(idx): """ Refresh automsg database, display automsg of idx, return idx. """ session.flush_event('automsg') autodb = DBProxy('automsg') automsgs = sorted(autodb.values()) if len(autodb) else db_firstrecord dblen = len(automsgs) # bounds check if idx < 0: idx = dblen - 1 elif idx > dblen - 1: idx = 0 tm_ago, handle, msg = automsgs[idx] asc_ago = u'%s ago' % (timeago(time.time() - tm_ago)) disp = (u''.join(( '\r\n\r\n', term.bold(handle.rjust(max_user)), term.bold_blue(u'/'), term.blue(u'%*d' % ( len('%d' % (dblen, )), idx, )), term.bold_blue(u':'), term.blue_reverse(msg.ljust(automsg_len)), term.bold(u'\\'), term.blue(asc_ago), ))) echo(u''.join(( u'\r\n\r\n', Ansi(disp).wrap(term.width), ))) return idx def refresh_all(idx=None): """ refresh screen, database, and return database index """ echo(u''.join(( u'\r\n\r\n', term.clear_eol, ))) for line in showcp437(artfile): echo(line) idx = refresh_automsg(-1 if idx is None else idx) refresh_prompt(prompt_msg) return idx idx = refresh_all() while True: if session.poll_event('refresh'): idx = refresh_all() elif session.poll_event('automsg'): refresh_automsg(-1) echo(u'\a') # bel refresh_prompt(prompt_msg) inp = getch(1) if inp in ( u'g', u'G', term.KEY_EXIT, unichr(27), unichr(3), ): # http://www.xfree86.org/4.5.0/ctlseqs.html # Restore xterm icon and window title from stack. echo(unichr(27) + u'[23;0t') echo(goodbye_msg) getch(1.5) disconnect('logoff.') elif inp in ( u'n', u'N', term.KEY_DOWN, term.KEY_NPAGE, ): idx = refresh_automsg(idx + 1) refresh_prompt(prompt_msg) elif inp in ( u'p', u'P', term.KEY_UP, term.KEY_PPAGE, ): idx = refresh_automsg(idx - 1) refresh_prompt(prompt_msg) elif inp in (u's', u'S'): # new prompt: say something ! refresh_prompt(prompt_say) msg = LineEditor(width=automsg_len).read() if msg is not None and msg.strip(): echo(u''.join(( u'\r\n\r\n', write_msg, ))) autodb = DBProxy('automsg') autodb.acquire() idx = max([int(ixx) for ixx in autodb.keys()] or [-1]) + 1 autodb[idx] = (time.time(), handle, msg.strip()) autodb.release() session.send_event('global', ( 'automsg', True, )) refresh_automsg(idx) echo(u''.join(( u'\r\n\r\n', commit_msg, ))) getch(0.5) # for effect, LoL # display prompt refresh_prompt(prompt_msg)
def denied(msg): """ Display denied message, pause for input for 1s. """ from x84.bbs import getterminal, echo, getch term = getterminal() echo(u'\r\n' + term.bold_red(msg)) getch(1.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
def main(): """ Main procedure. """ # pylint: disable=R0912 # Too many branches from x84.bbs import getsession, getterminal, echo, getch, Ansi, from_cp437 session, term = getsession(), getterminal() session.activity = u'Selecting chracter set' artfile = os.path.join( os.path.dirname(__file__), 'art', ( 'plant-256.ans' if term.number_of_colors == 256 else 'plant.ans')) enc_prompt = ( u'Press left/right until artwork looks best. Clients should' ' select utf8 encoding and Andale Mono font. Older clients or' ' clients with appropriate 8-bit fontsets can select cp437, though' ' some characters may appear as "?".') save_msg = u"\r\n\r\n'%s' is now your preferred encoding ..\r\n" if session.user.get('expert', False): echo(u'\r\n\r\n(U) UTF-8 encoding or (C) CP437 encoding [uc] ?\b\b') while True: inp = getch() if inp in (u'u', u'U'): session.encoding = 'utf8' break elif inp in (u'c', u'C'): session.encoding = 'cp437' break session.user['charset'] = session.encoding echo(save_msg % (session.encoding,)) getch(1.0) return art = (from_cp437(open(artfile).read()).splitlines() if os.path.exists(artfile) else [u'']) def refresh(sel): """ Refresh art and yes/no prompt, ``sel``. """ session.flush_event('refresh') session.encoding = selector.selection if sel.selection == 'utf8': # ESC %G activates UTF-8 with an unspecified implementation # level from ISO 2022 in a way that allows to go back to # ISO 2022 again. echo(unichr(27) + u'%G') elif sel.selection == 'cp437': # ESC %@ returns to ISO 2022 in case UTF-8 had been entered. # ESC ) U Sets character set G1 to codepage 437 .. usually. echo(unichr(27) + u'%@') echo(unichr(27) + u')U') else: assert False, "Only encodings 'utf8' and 'cp437' supported." # display art, banner, paragraph, refresh selector refresh buf = [line for line in art] return u''.join(( u'\r\n\r\n', u'\r\n'.join(buf), u'\r\n\r\n', Ansi(enc_prompt).wrap(int(term.width * .95)), u'\r\n\r\n', sel.refresh(),)) selector = get_selector(session.encoding) echo(refresh(selector)) while True: inp = getch(1) if inp == term.KEY_ENTER: session.user['charset'] = session.encoding echo(save_msg % (session.encoding,)) getch(1.0) return elif inp is not None: selector.process_keystroke(inp) if selector.quit: # 'escape' quits without save, though the encoding # has been temporarily set for this session. return if selector.moved: # set and refresh art in new encoding echo(refresh(selector)) if session.poll_event('refresh') is not None: # instantiate a new selector in case the window size has changed. selector = get_selector(session.encoding) echo(refresh(selector))
def main(): """ Main procedure. """ # pylint: disable=R0912,R0914,R0915 # Too many branches # Too many local variables # Too many statements from x84.bbs import getsession, getterminal, getch, echo session, term = getsession(), getterminal() ayt_lastfresh = 0 def broadcast_ayt(last_update): """ Globally boradcast 'are-you-there' request. """ if time.time() - last_update > POLL_AYT: session.send_event('global', ('AYT', session.sid,)) last_update = time.time() return last_update sessions = dict() dirty = time.time() cur_row = 0 while True: ayt_lastfresh = broadcast_ayt(ayt_lastfresh) inp = getch(POLL_KEY) if session.poll_event('refresh') or ( inp in (u' ', term.KEY_REFRESH, unichr(12))): dirty = time.time() cur_row = 0 elif inp in (u'q', 'Q', term.KEY_EXIT, unichr(27)): return elif inp in (u'c', 'C'): cur_row = 0 if chat(sessions) else cur_row dirty = time.time() elif inp in (u's', 'S'): cur_row = 0 if sendmsg(sessions) else cur_row dirty = time.time() elif inp is not None and 'sysop' in session.user.groups: if inp in (u'e', u'E'): cur_row = 0 if edit(sessions) else cur_row dirty = time.time() elif inp in (u'p', u'P'): cur_row = 0 if playback(sessions) else cur_row dirty = time.time() elif inp in (u'w', u'W'): cur_row = 0 if watch(sessions) else cur_row dirty = time.time() elif inp in (u'v', u'V'): cur_row = 0 if view(sessions) else cur_row + 3 dirty = time.time() elif inp in (u'd', u'D'): disconnect(sessions) dirty = time.time() # add sessions that respond to AYT data = session.poll_event('ACK') if data is not None: sid, handle = data if sid in sessions: sessions[sid]['handle'] = handle else: sessions[sid] = dict(( ('handle', handle), ('lastfresh', time.time()), ('lastasked', time.time()),)) dirty = time.time() echo(u'\a') # update sessions that respond to info-req data = session.poll_event('info-ack') if data is not None: sid, attrs = data if sessions.get(sid, dict()).get('activity') != attrs['activity']: # and refresh screen if activity changes dirty = time.time() sessions[sid] = attrs sessions[sid]['lastfresh'] = time.time() # update our own session sessions[SELF_ID] = session.info() sessions[SELF_ID]['lastfresh'] = time.time() # request that all sessions update if more stale than POLL_INF, # or is missing session info (only AYT replied so far!), # or has been displayed as 'Disconnected' (marked for deletion) for sid, attrs in sessions.items(): if sid == SELF_ID: continue if attrs.get('idle', -1) == -1 or ( time.time() - attrs.get('lastfresh', 0) > POLL_INF and time.time() - attrs.get('lastasked', 0) > POLL_INF): request_info(sid) attrs['lastasked'] = time.time() # prune users who haven't responded to AYT for sid, attrs in sessions.items(): if time.time() - attrs['lastfresh'] > (POLL_AYT * 2): sessions[sid]['delete'] = 1 dirty = time.time() if dirty is not None and time.time() - dirty > POLL_OUT: session.activity = u"Who's Online" otxt = describe(sessions) olen = len(otxt.splitlines()) if 0 == cur_row or (cur_row + olen) >= term.height: otxt_b = banner() otxt_h = heading(sessions) cur_row = len(otxt_b.splitlines()) + len(otxt_h.splitlines()) echo(u'\r\n'.join((u'\r\n\r\n', otxt_b, otxt_h, otxt))) else: echo(u''.join(( u'\r\n', '-'.center(term.width).rstrip(), u'\r\n'))) echo(otxt) cur_row += olen dirty = None # delete disconnected sessions for sid, attrs in sessions.items()[:]: if attrs.get('delete', 0) == 1: del sessions[sid]
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' 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'))) else: SEARCH_TAGS = set(['public']) # 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>', ])) 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, 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 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 # read target messages read_messages(msgs, new)
def get_username(handle=u''): """ Prompt for a login handle. If unfound, script change to 'nua' when allow_apply is enabled (default=yes). Also allow 'anonymous' when enabled (default=no). A unicode handle of non-zero length is returned when the login handle matches a userbase record. """ # pylint: disable=R0914,R0911 # Too many local variables # Too many return statements from x84.bbs import getterminal, ini, echo, LineEditor, gosub, goto from x84.bbs import find_user, getch term = getterminal() prompt_user = u'user: '******'\r\n\r\n --> Create new account? [ynq] <--' + '\b' * 5 allow_apply = ini.CFG.getboolean('nua', 'allow_apply') enable_anonymous = ini.CFG.getboolean('matrix', 'enable_anonymous') # pylint: disable=E1103 # Instance of '_Chainmap' has no 'split' member # (but some types could not be inferred) newcmds = ini.CFG.get('matrix', 'newcmds').split() byecmds = ini.CFG.get('matrix', 'byecmds').split() denied_msg = u'\r\n\r\nfiRSt, YOU MUSt AbANdON YOUR libERtIES.' badanon_msg = u"\r\n " + term.bright_red + u"'%s' login denied." max_user = ini.CFG.getint('nua', 'max_user') nuascript = ini.CFG.get('nua', 'script') topscript = ini.CFG.get('matrix', 'topscript') echo(prompt_user) handle = LineEditor(max_user, handle).read() if handle is None or 0 == len(handle.strip()): echo(u'\r\n') return u'' elif handle.lower() in newcmds: if allow_apply: gosub('nua', u'') return u'' denied(denied_msg) return u'' elif handle.lower() in byecmds: goto('logoff') elif handle.lower() == u'anonymous': if enable_anonymous: goto(topscript, 'anonymous') denied(badanon_msg % (handle, )) return u'' u_handle = find_user(handle) if u_handle is not None: return u_handle # matched if allow_apply is False: denied(denied_msg) return u'' echo(apply_msg) ynq = getch() if ynq in (u'q', u'Q', term.KEY_EXIT): # goodbye goto('logoff') elif ynq in (u'y', u'Y'): # new user application goto(nuascript, handle) echo(u'\r\n') return u''