def test_binaryops(self): a = flowfilter.parse("~u foobar | ~h voing") isinstance(a, flowfilter.FOr) self._dump(a) a = flowfilter.parse("~u foobar & ~h voing") isinstance(a, flowfilter.FAnd) self._dump(a)
def response(self, flow): try: data = flow.response.content data = json.loads(data) if flowfilter.match( flowfilter.parse( '~u https://qf.56.com/wxexam/v2/singleMode/getQuestion.do' ), flow): global Question, Option Question = question = data['data']['title'] answer = self.m.db.col.find_one({"question": question}) Option = option = [ data['data']['answera'], data['data']['answerb'], data['data']['answerc'] ] if answer is None: print(question) else: trueAnsewer = '' strindex = '' print('have answer') c = flow.response.content for index in range(len(Option)): if (Option[index] == answer['answer']): if index == 1: strindex = 'answera' elif index == 2: strindex = 'answerb' else: strindex = 'answerc' trueAnsewer = data['data'][strindex] + '[标准答案]' flow.response.content = flow.response.content.replace( bytes(data['data'][strindex], encoding='utf8'), bytes(trueAnsewer, encoding='utf8')) # print(flow.response.content) return if flowfilter.match( flowfilter.parse( '~u https://qf.56.com/wxexam/v2/singleMode/answer.do'), flow): answer = data['data']['rightAnswer'] _answer = self.m.db.col.find_one({"question": Question}) if _answer is None: print('插入' + { "question": Question, "answer": Option[answer - 1] }) self.m.db.col.insert({ "question": Question, "answer": Option[answer - 1] }) else: print('更新') self.m.db.col.update({"question": Question}, {"answer": Option[answer - 1]}) return except Exception: pass
def __init__(self, path): # 保存结果的 folder 路径 self.folder_path = path # 构造一个 HTTP response code self.http_code_ok = flowfilter.parse('~c 200') #根据路径生成 if not os.path.exists(self.folder_path): os.makedirs(self.folder_path) # 构造一个 URL 过滤器 self.douban_path = flowfilter.parse('~u rtlog.resso.app')
def test_signals(): v = view.View() rec_add = Record() rec_update = Record() rec_remove = Record() rec_refresh = Record() def clearrec(): rec_add.calls = [] rec_update.calls = [] rec_remove.calls = [] rec_refresh.calls = [] v.sig_view_add.connect(rec_add) v.sig_view_update.connect(rec_update) v.sig_view_remove.connect(rec_remove) v.sig_view_refresh.connect(rec_refresh) assert not any([rec_add, rec_update, rec_remove, rec_refresh]) # Simple add v.add([tft()]) assert rec_add assert not any([rec_update, rec_remove, rec_refresh]) # Filter change triggers refresh clearrec() v.set_filter(flowfilter.parse("~m put")) assert rec_refresh assert not any([rec_update, rec_add, rec_remove]) v.set_filter(flowfilter.parse("~m get")) # An update that results in a flow being added to the view clearrec() v[0].request.method = "PUT" v.update([v[0]]) assert rec_remove assert not any([rec_update, rec_refresh, rec_add]) # An update that does not affect the view just sends update v.set_filter(flowfilter.parse("~m put")) clearrec() v.update([v[0]]) assert rec_update assert not any([rec_remove, rec_refresh, rec_add]) # An update for a flow in state but not view does not do anything f = v[0] v.set_filter(flowfilter.parse("~m get")) assert not len(v) clearrec() v.update([f]) assert not any([rec_add, rec_update, rec_remove, rec_refresh])
def test_focus(): # Special case - initialising with a view that already contains data v = view.View() v.add(tft()) f = view.Focus(v) assert f.index is 0 assert f.flow is v[0] # Start empty v = view.View() f = view.Focus(v) assert f.index is None assert f.flow is None v.add(tft(start=1)) assert f.index == 0 assert f.flow is v[0] v.add(tft(start=0)) assert f.index == 1 assert f.flow is v[1] v.add(tft(start=2)) assert f.index == 1 assert f.flow is v[1] v.remove(v[1]) assert f.index == 1 assert f.flow is v[1] v.remove(v[1]) assert f.index == 0 assert f.flow is v[0] v.remove(v[0]) assert f.index is None assert f.flow is None v.add(tft(method="get", start=0)) v.add(tft(method="get", start=1)) v.add(tft(method="put", start=2)) v.add(tft(method="get", start=3)) f.flow = v[2] assert f.flow.request.method == "PUT" filt = flowfilter.parse("~m get") v.set_filter(filt) assert f.index == 2 filt = flowfilter.parse("~m oink") v.set_filter(filt) assert f.index is None
def test_simple(self): assert flowfilter.parse("~q") assert flowfilter.parse("~c 10") assert flowfilter.parse("~m foobar") assert flowfilter.parse("~u foobar") assert flowfilter.parse("~q ~c 10") assert flowfilter.parse("~replay") assert flowfilter.parse("~replayq") assert flowfilter.parse("~replays") assert flowfilter.parse("~comment .") p = flowfilter.parse("~q ~c 10") self._dump(p) assert len(p.lst) == 2
def test_quoting(self): a = flowfilter.parse("~u 'foo ~u bar' ~u voing") assert a.lst[0].expr == "foo ~u bar" assert a.lst[1].expr == "voing" self._dump(a) a = flowfilter.parse("~u foobar") assert a.expr == "foobar" a = flowfilter.parse(r"~u 'foobar\"\''") assert a.expr == "foobar\"'" a = flowfilter.parse(r'~u "foo \'bar"') assert a.expr == "foo 'bar"
def __call__(self, f: http.HTTPFlow): for flt, api in self.flts: if flt(f): self.current_api = api return True if len(self.__temp) == 0: # print('*'*100) return False for path in self.__temp: a = self.url_a_dict[path] r = f.request api = a if isinstance(a, str): api = Api(a) self.__temp.remove(path) flt = flowfilter.parse(api.url) self.flts.append((flt, api)) if r.path.startswith(path): self.current_api = api return True return False
def configure(self, options, updated): """ .replacements is a list of tuples (fpat, rex, s): fpatt: a string specifying a filter pattern. rex: a regular expression, as bytes. s: the replacement string, as bytes """ if self.optionName in updated: lst = [] for rep in getattr(options, self.optionName): if isinstance(rep, str): fpatt, rex, s = parse_hook(rep) else: fpatt, rex, s = rep flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid filter pattern: %s" % fpatt ) try: re.compile(rex) except re.error as e: raise exceptions.OptionsError( "Invalid regular expression: %s - %s" % (rex, str(e)) ) lst.append((rex, s, flt)) self.lst = lst
def __init__(self, file_path: Union[str, Path], append: bool = True, index_filter: Union[str, flowfilter.TFilter] = filter_404, index_format: str = "json"): """ Initializes the urlindex add-on. Args: file_path: Path to file to which the URL index will be written. Can either be given as str or Path. append: Bool to decide whether to append new URLs to the given file (as opposed to overwrite the contents of the file) index_filer: A mitmproxy filter with which the seen URLs will be filtered before being written. Can either be given as str or as flowfilter.TFilter index_format: The format of the URL index, can either be "json" or "text". """ if isinstance(index_filter, str): self.index_filter = flowfilter.parse(index_filter) if self.index_filter is None: raise ValueError("Invalid filter expression.") else: self.index_filter = index_filter file_path = Path(file_path) try: self.writer = WRITER[index_format.lower()](file_path) except KeyError: raise ValueError(f"Format '{index_format}' is not supported.") if not append and file_path.exists(): file_path.unlink() self.writer.load()
def configure(self, options, updated): """ .replacements is a list of tuples (fpat, rex, s): fpatt: a string specifying a filter pattern. rex: a regular expression, as bytes. s: the replacement string, as bytes """ if self.optionName in updated: lst = [] for rep in getattr(options, self.optionName): if isinstance(rep, str): fpatt, rex, s = parse_hook(rep) else: fpatt, rex, s = rep flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid filter pattern: %s" % fpatt) try: re.compile(rex) except re.error as e: raise exceptions.OptionsError( "Invalid regular expression: %s - %s" % (rex, str(e))) lst.append((rex, s, flt)) self.lst = lst
def configure(self, updated): """ .replacements is a list of tuples (fpat, rex, s): fpatt: a string specifying a filter pattern. rex: a regular expression, as string. s: the replacement string """ if "replacements" in updated: lst = [] for rep in ctx.options.replacements: fpatt, rex, s = parse_hook(rep) flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid filter pattern: %s" % fpatt ) try: # We should ideally escape here before trying to compile re.compile(rex) except re.error as e: raise exceptions.OptionsError( "Invalid regular expression: %s - %s" % (rex, str(e)) ) if s.startswith("@") and not os.path.isfile(s[1:]): raise exceptions.OptionsError( "Invalid file path: {}".format(s[1:]) ) lst.append((rex, s, flt)) self.lst = lst
def __init__(self): #添加一个过滤器,只处理问题包 self.filter = flowfilter.parse('~u findQuiz') #连接答案数据库 self.conn = MongoClient('localhost', 27017) self.db = self.conn.tnwz self.answer_set = self.db.quizzes
def test_filter(): v = view.View() f = flowfilter.parse("~m get") v.request(tft(method="get")) v.request(tft(method="put")) v.request(tft(method="get")) v.request(tft(method="put")) assert(len(v)) == 4 v.set_filter(f) assert [i.request.method for i in v] == ["GET", "GET"] assert len(v._store) == 4 v.set_filter(None) assert len(v) == 4 v.toggle_marked() assert len(v) == 0 v.toggle_marked() assert len(v) == 4 v[1].marked = True v.toggle_marked() assert len(v) == 1 assert v[0].marked v.toggle_marked() assert len(v) == 4
def configure(self, updated): """ .replacements is a list of tuples (fpat, rex, s): fpatt: a string specifying a filter pattern. rex: a regular expression, as string. s: the replacement string """ if "replacements" in updated: lst = [] for rep in ctx.options.replacements: fpatt, rex, s = parse_hook(rep) flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid filter pattern: %s" % fpatt) try: # We should ideally escape here before trying to compile re.compile(rex) except re.error as e: raise exceptions.OptionsError( "Invalid regular expression: %s - %s" % (rex, str(e))) if s.startswith("@") and not os.path.isfile(s[1:]): raise exceptions.OptionsError( "Invalid file path: {}".format(s[1:])) lst.append((rex, s, flt)) self.lst = lst
def resolve(self, flow_spec: str) -> typing.Sequence[mitmproxy.flow.Flow]: """ Resolve a flow list specification to an actual list of flows. """ if flow_spec == "@all": return [i for i in self._store.values()] if flow_spec == "@focus": return [self.focus.flow] if self.focus.flow else [] elif flow_spec == "@shown": return [i for i in self] elif flow_spec == "@hidden": return [i for i in self._store.values() if i not in self._view] elif flow_spec == "@marked": return [i for i in self._store.values() if i.marked] elif flow_spec == "@unmarked": return [i for i in self._store.values() if not i.marked] elif re.match(r"@[0-9a-f\-,]{36,}", flow_spec): ids = flow_spec[1:].split(",") return [i for i in self._store.values() if i.id in ids] else: try: filt = flowfilter.parse(flow_spec) except ValueError as e: raise exceptions.CommandError(str(e)) from e return [i for i in self._store.values() if filt(i)]
def configure(self, options, updated): if options.stickyauth: flt = flowfilter.parse(options.stickyauth) if not flt: raise exceptions.OptionsError( "stickyauth: invalid filter expression: %s" % options.stickyauth ) self.flt = flt
def set_filter(self, input_filter: typing.Optional[str]) -> None: filt = matchall if not input_filter else flowfilter.parse(input_filter) if not filt: raise CommandError( "Invalid interception filter: %s" % filt ) self.filter = filt self._refilter()
def set_intercept(self, txt): if txt: flt = flowfilter.parse(txt) if not flt: return "Invalid filter expression." self.intercept = flt else: self.intercept = None
def configure(self, options, updated): if options.stickycookie: flt = flowfilter.parse(options.stickycookie) if not flt: raise exceptions.OptionsError( "stickycookie: invalid filter expression: %s" % options.stickycookie) self.flt = flt
def configure(self, updated): if "dumper_filter" in updated: if ctx.options.dumper_filter: try: self.filter = flowfilter.parse(ctx.options.dumper_filter) except ValueError as e: raise exceptions.OptionsError(str(e)) from e else: self.filter = None
def configure(self, updated): if "stickycookie" in updated: if ctx.options.stickycookie: try: self.flt = flowfilter.parse(ctx.options.stickycookie) except ValueError as e: raise exceptions.OptionsError(str(e)) from e else: self.flt = None
def configure(self, opts, updated): if "intercept" in updated: if not opts.intercept: self.filt = None return self.filt = flowfilter.parse(opts.intercept) if not self.filt: raise exceptions.OptionsError( "Invalid interception filter: %s" % opts.intercept)
def configure(self, updated): if "readfile_filter" in updated: filt = None if ctx.options.readfile_filter: filt = flowfilter.parse(ctx.options.readfile_filter) if not filt: raise exceptions.OptionsError( "Invalid readfile filter: %s" % ctx.options.readfile_filter ) self.filter = filt
def configure(self, updated): if "dumper_filter" in updated: if ctx.options.dumper_filter: self.filter = flowfilter.parse(ctx.options.dumper_filter) if not self.filter: raise exceptions.OptionsError( "Invalid filter expression: %s" % ctx.options.dumper_filter) else: self.filter = None
def configure(self, updated): if "intercept" in updated: if not ctx.options.intercept: self.filt = None return self.filt = flowfilter.parse(ctx.options.intercept) if not self.filt: raise exceptions.OptionsError( "Invalid interception filter: %s" % ctx.options.intercept )
def configure(self, updated: Set[str]) -> None: if 'serve_userscript' not in updated: return if ctx.options.serve_userscript: try: self.filter = flowfilter.parse(ctx.options.serve_userscript) except Exception as e: raise OptionsError(f'Cannot parse serve_userscript option {option}: {e}.') from e else: self.filter = None
def configure(self, updated): if "view_filter" in updated: if ctx.options.view_filter: self.filter = flowfilter.parse(ctx.options.view_filter) if not self.filter: raise exceptions.OptionsError( "Invalid filter expression: %s" % ctx.options.view_filter ) else: self.filter = None
def is_error(self, col, val): if col == 0: if not flowfilter.parse(val): return "Invalid filter specification." elif col == 1: try: re.compile(val) except re.error: return "Invalid regular expression." return False
def configure(self, updated): if "intercept" in updated: if ctx.options.intercept: self.filt = flowfilter.parse(ctx.options.intercept) if not self.filt: raise exceptions.OptionsError(f"Invalid interception filter: {ctx.options.intercept}") ctx.options.intercept_active = True else: self.filt = None ctx.options.intercept_active = False
def set_filter_cmd(self, filter_expr: str) -> None: """ Sets the current view filter. """ filt = None if filter_expr: try: filt = flowfilter.parse(filter_expr) except ValueError as e: raise exceptions.CommandError(str(e)) from e self.set_filter(filt)
def configure(self, updated): if "stickycookie" in updated: if ctx.options.stickycookie: flt = flowfilter.parse(ctx.options.stickycookie) if not flt: raise exceptions.OptionsError( "stickycookie: invalid filter expression: %s" % ctx.options.stickycookie ) self.flt = flt else: self.flt = None
def configure(self, options, updated): if options.filtstr: self.filter = flowfilter.parse(options.filtstr) if not self.filter: raise exceptions.OptionsError("Invalid filter expression: %s" % options.filtstr) else: self.filter = None self.flow_detail = options.flow_detail self.outfp = options.tfile self.showhost = options.showhost
def configure(self, updated): if "stickycookie" in updated: if ctx.options.stickycookie: flt = flowfilter.parse(ctx.options.stickycookie) if not flt: raise exceptions.OptionsError( "stickycookie: invalid filter expression: %s" % ctx.options.stickycookie) self.flt = flt else: self.flt = None
def configure(self, updates): if "sleep" in updates: sleep = ctx.options.sleep if sleep and sleep < 0: raise OptionsError("'sleep' must be >= 0") if "sleep_filter" in updates: filt_str = ctx.options.sleep_filter filt = matchall if not filt_str else flowfilter.parse(filt_str) if not filt: raise OptionsError("Invalid filter expression: %s" % filt_str) self.filter = filt
def set_filter_cmd(self, f: str) -> None: """ Sets the current view filter. """ filt = None if f: filt = flowfilter.parse(f) if not filt: raise exceptions.CommandError( "Invalid interception filter: %s" % f) self.set_filter(filt)
def configure(self, updated): if "intercept" in updated: if not ctx.options.intercept: self.filt = None ctx.options.intercept_active = False return self.filt = flowfilter.parse(ctx.options.intercept) if not self.filt: raise exceptions.OptionsError( "Invalid interception filter: %s" % ctx.options.intercept) ctx.options.intercept_active = True
def set_view_filter(self, txt): if txt == self.filter_txt: return if txt: flt = flowfilter.parse(txt) if not flt: return "Invalid filter expression." self.view._close() self.view = FlowView(self.flows, flt) else: self.view._close() self.view = FlowView(self.flows, None)
def set_filter_cmd(self, f: str) -> None: """ Sets the current view filter. """ filt = None if f: filt = flowfilter.parse(f) if not filt: raise exceptions.CommandError( "Invalid interception filter: %s" % f ) self.set_filter(filt)
def test_simple(self): assert not flowfilter.parse("~b") assert flowfilter.parse("~q") assert flowfilter.parse("~c 10") assert flowfilter.parse("~m foobar") assert flowfilter.parse("~u foobar") assert flowfilter.parse("~q ~c 10") p = flowfilter.parse("~q ~c 10") self._dump(p) assert len(p.lst) == 2
def configure(self, options, updated): if options.filtstr: self.filter = flowfilter.parse(options.filtstr) if not self.filter: raise exceptions.OptionsError( "Invalid filter expression: %s" % options.filtstr ) else: self.filter = None self.flow_detail = options.flow_detail self.outfp = options.tfile self.showhost = options.showhost
def configure(self, updated): if "setheaders" in updated: self.lst = [] for shead in ctx.options.setheaders: fpatt, header, value = parse_setheader(shead) flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid setheader filter pattern %s" % fpatt ) self.lst.append((fpatt, header, value, flt))
def configure(self, options, updated): if "filtstr" in updated: if options.filtstr: self.filter = flowfilter.parse(options.filtstr) if not self.filter: raise exceptions.OptionsError( "Invalid filter expression: %s" % options.filtstr ) else: self.filter = None self.flow_detail = options.flow_detail self.showhost = options.showhost self.default_contentview = options.default_contentview
def configure(self, options, updated): """ options.setheaders is a tuple of (fpatt, header, value) fpatt: String specifying a filter pattern. header: Header name. value: Header value string """ for fpatt, header, value in options.setheaders: flt = flowfilter.parse(fpatt) if not flt: raise exceptions.OptionsError( "Invalid setheader filter pattern %s" % fpatt ) self.lst.append((fpatt, header, value, flt))
def test_filter(self): sio = io.BytesIO() flt = flowfilter.parse("~c 200") w = mitmproxy.io.FilteredFlowWriter(sio, flt) f = tflow.tflow(resp=True) f.response.status_code = 200 w.add(f) f = tflow.tflow(resp=True) f.response.status_code = 201 w.add(f) sio.seek(0) r = mitmproxy.io.FlowReader(sio) assert len(list(r.stream()))
def configure(self, updated): # We're already streaming - stop the previous stream and restart if "save_stream_filter" in updated: if ctx.options.save_stream_filter: self.filt = flowfilter.parse(ctx.options.save_stream_filter) if not self.filt: raise exceptions.OptionsError( "Invalid filter specification: %s" % ctx.options.save_stream_filter ) else: self.filt = None if "save_stream_file" in updated or "save_stream_filter" in updated: if self.stream: self.done() if ctx.options.save_stream_file: self.start_stream_to_path(ctx.options.save_stream_file, self.filt)
def test_resolve(): v = view.View() with taddons.context() as tctx: assert tctx.command(v.resolve, "@all") == [] assert tctx.command(v.resolve, "@focus") == [] assert tctx.command(v.resolve, "@shown") == [] assert tctx.command(v.resolve, "@hidden") == [] assert tctx.command(v.resolve, "@marked") == [] assert tctx.command(v.resolve, "@unmarked") == [] assert tctx.command(v.resolve, "~m get") == [] v.request(tft(method="get")) assert len(tctx.command(v.resolve, "~m get")) == 1 assert len(tctx.command(v.resolve, "@focus")) == 1 assert len(tctx.command(v.resolve, "@all")) == 1 assert len(tctx.command(v.resolve, "@shown")) == 1 assert len(tctx.command(v.resolve, "@unmarked")) == 1 assert tctx.command(v.resolve, "@hidden") == [] assert tctx.command(v.resolve, "@marked") == [] v.request(tft(method="put")) assert len(tctx.command(v.resolve, "@focus")) == 1 assert len(tctx.command(v.resolve, "@shown")) == 2 assert len(tctx.command(v.resolve, "@all")) == 2 assert tctx.command(v.resolve, "@hidden") == [] assert tctx.command(v.resolve, "@marked") == [] v.request(tft(method="get")) v.request(tft(method="put")) f = flowfilter.parse("~m get") v.set_filter(f) v[0].marked = True def m(l): return [i.request.method for i in l] assert m(tctx.command(v.resolve, "~m get")) == ["GET", "GET"] assert m(tctx.command(v.resolve, "~m put")) == ["PUT", "PUT"] assert m(tctx.command(v.resolve, "@shown")) == ["GET", "GET"] assert m(tctx.command(v.resolve, "@hidden")) == ["PUT", "PUT"] assert m(tctx.command(v.resolve, "@marked")) == ["GET"] assert m(tctx.command(v.resolve, "@unmarked")) == ["PUT", "GET", "PUT"] assert m(tctx.command(v.resolve, "@all")) == ["GET", "PUT", "GET", "PUT"] with pytest.raises(exceptions.CommandError, match="Invalid flow filter"): tctx.command(v.resolve, "~")
def configure(self, options, updated): # We're already streaming - stop the previous stream and restart if "filtstr" in updated: if options.get("filtstr"): self.filt = flowfilter.parse(options.filtstr) if not self.filt: raise exceptions.OptionsError( "Invalid filter specification: %s" % options.filtstr ) if "streamfile" in updated: if self.stream: self.done() if options.streamfile: if options.streamfile_append: mode = "ab" else: mode = "wb" self.start_stream_to_path(options.streamfile, mode, self.filt)
def configure(self, opts, updated): if "filter" in updated: filt = None if opts.filter: filt = flowfilter.parse(opts.filter) if not filt: raise exceptions.OptionsError("Invalid interception filter: %s" % opts.filter) self.set_filter(filt) if "order" in updated: if opts.order is None: self.set_order(self.default_order) else: if opts.order not in self.orders: raise exceptions.OptionsError("Unknown flow order: %s" % opts.order) self.set_order(self.orders[opts.order]) if "order_reversed" in updated: self.set_reversed(opts.order_reversed) if "focus_follow" in updated: self.focus_follow = opts.focus_follow
def configure(self, options, updated): # We're already streaming - stop the previous stream and restart if self.stream: self.done() if options.outfile: flt = None if options.get("filtstr"): flt = flowfilter.parse(options.filtstr) if not flt: raise exceptions.OptionsError( "Invalid filter specification: %s" % options.filtstr ) path, mode = options.outfile if mode not in ("wb", "ab"): raise exceptions.OptionsError("Invalid mode.") err = self.start_stream_to_path(path, mode, flt) if err: raise exceptions.OptionsError(err)
def test_update(): v = view.View() flt = flowfilter.parse("~m get") v.set_filter(flt) f = tft(method="get") v.request(f) assert f in v f.request.method = "put" v.update([f]) assert f not in v f.request.method = "get" v.update([f]) assert f in v v.update([f]) assert f in v
def configure(self, updated): if "view_filter" in updated: filt = None if ctx.options.view_filter: filt = flowfilter.parse(ctx.options.view_filter) if not filt: raise exceptions.OptionsError( "Invalid interception filter: %s" % ctx.options.view_filter ) self.set_filter(filt) if "console_order" in updated: if ctx.options.console_order not in self.orders: raise exceptions.OptionsError( "Unknown flow order: %s" % ctx.options.console_order ) self.set_order(self.orders[ctx.options.console_order]) if "console_order_reversed" in updated: self.set_reversed(ctx.options.console_order_reversed) if "console_focus_follow" in updated: self.focus_follow = ctx.options.console_focus_follow