primitive, awful text formatting
This commit is contained in:
		
							parent
							
								
									28680865ee
								
							
						
					
					
						commit
						b731ab69fa
					
				| @ -408,7 +408,7 @@ class BBJ(object): | |||||||
|         return response["data"], response["usermap"] |         return response["data"], response["usermap"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def thread_load(self, thread_id): |     def thread_load(self, thread_id, format=None): | ||||||
|         """ |         """ | ||||||
|         Returns a tuple where [0] is a thread object and [1] is a usermap object. |         Returns a tuple where [0] is a thread object and [1] is a usermap object. | ||||||
| 
 | 
 | ||||||
| @ -419,5 +419,5 @@ class BBJ(object): | |||||||
|               print(usermap[author_id]["user_name"]) |               print(usermap[author_id]["user_name"]) | ||||||
|               print(message["body"]) |               print(message["body"]) | ||||||
|         """ |         """ | ||||||
|         response = self("thread_load", thread_id=thread_id) |         response = self("thread_load", format=format, thread_id=thread_id) | ||||||
|         return response["data"], response["usermap"] |         return response["data"], response["usermap"] | ||||||
|  | |||||||
| @ -62,6 +62,11 @@ welcome = """>>> Welcome to Bulletin Butter & Jelly! ------------------@ | |||||||
| @_________________________________________________________@ | @_________________________________________________________@ | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
|  | colornames = [ | ||||||
|  |     "none", "red", "yellow", "green", "blue", | ||||||
|  |     "cyan", "magenta" | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| colors = [ | colors = [ | ||||||
|     "\033[1;31m", "\033[1;33m", "\033[1;33m", |     "\033[1;31m", "\033[1;33m", "\033[1;33m", | ||||||
|     "\033[1;32m", "\033[1;34m", "\033[1;35m" |     "\033[1;32m", "\033[1;34m", "\033[1;35m" | ||||||
| @ -90,10 +95,13 @@ class App(object): | |||||||
|             ("default", "default", "default"), |             ("default", "default", "default"), | ||||||
|             ("bar", "light magenta", "default"), |             ("bar", "light magenta", "default"), | ||||||
|             ("button", "light red", "default"), |             ("button", "light red", "default"), | ||||||
|  |             ("quote", "light green,underline", "default"), | ||||||
|             ("opt_prompt", "black", "light gray"), |             ("opt_prompt", "black", "light gray"), | ||||||
|             ("opt_header", "light cyan", "default"), |             ("opt_header", "light cyan", "default"), | ||||||
|             ("hover", "light cyan", "default"), |             ("hover", "light cyan", "default"), | ||||||
|             ("dim", "dark gray", "default"), |             ("dim", "dark gray", "default"), | ||||||
|  |             ("bold", "default,bold", "default"), | ||||||
|  |             ("underline", "default,underline", "default"), | ||||||
| 
 | 
 | ||||||
|             # map the bbj api color values for display |             # map the bbj api color values for display | ||||||
|             ("0", "default", "default"), |             ("0", "default", "default"), | ||||||
| @ -258,7 +266,7 @@ class App(object): | |||||||
|         return [ |         return [ | ||||||
|             head, |             head, | ||||||
|             urwid.Divider(), |             urwid.Divider(), | ||||||
|             MessageBody(message["body"]), |             urwid.Columns([(self.prefs["max_text_width"], MessageBody(message["body"]))]), | ||||||
|             urwid.Divider(), |             urwid.Divider(), | ||||||
|             urwid.AttrMap(urwid.Divider("-"), "dim") |             urwid.AttrMap(urwid.Divider("-"), "dim") | ||||||
|         ] |         ] | ||||||
| @ -333,7 +341,7 @@ class App(object): | |||||||
|         if self.mode == "index": |         if self.mode == "index": | ||||||
|             self.last_pos = self.loop.widget.body.base_widget.get_focus()[1] |             self.last_pos = self.loop.widget.body.base_widget.get_focus()[1] | ||||||
|         self.mode = "thread" |         self.mode = "thread" | ||||||
|         thread, usermap = network.thread_load(thread_id) |         thread, usermap = network.thread_load(thread_id, format="sequential") | ||||||
|         self.usermap.update(usermap) |         self.usermap.update(usermap) | ||||||
|         self.thread = thread |         self.thread = thread | ||||||
|         self.walker.clear() |         self.walker.clear() | ||||||
| @ -480,6 +488,11 @@ class App(object): | |||||||
|         widget.set_text(rendered) |         widget.set_text(rendered) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     def edit_width(self, editor, content): | ||||||
|  |         self.prefs["max_text_width"] = \ | ||||||
|  |             int(content) if content else 0 | ||||||
|  |         bbjrc("update", **self.prefs) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     def options_menu(self): |     def options_menu(self): | ||||||
|         """ |         """ | ||||||
| @ -488,14 +501,13 @@ class App(object): | |||||||
|         """ |         """ | ||||||
|         editor_buttons = [] |         editor_buttons = [] | ||||||
|         edit_mode = [] |         edit_mode = [] | ||||||
|         user_colors = [] |  | ||||||
| 
 | 
 | ||||||
|         if network.user_auth: |         if network.user_auth: | ||||||
|             account_message = "Logged in as %s." % network.user_name |             account_message = "Logged in as %s." % network.user_name | ||||||
|             colors = ["None", "Red", "Yellow", "Green", "Blue", "Cyan", "Magenta"] |             user_colors = [] | ||||||
|             for index, color in enumerate(colors): |             for index, color in enumerate(colornames): | ||||||
|                 urwid.RadioButton( |                 urwid.RadioButton( | ||||||
|                     user_colors, color, |                     user_colors, color.title(), | ||||||
|                     network.user["color"] == index, |                     network.user["color"] == index, | ||||||
|                     self.set_color, index) |                     self.set_color, index) | ||||||
| 
 | 
 | ||||||
| @ -513,11 +525,11 @@ class App(object): | |||||||
|             account_stuff = [urwid.Button("Login/Register", on_press=self.relog)] |             account_stuff = [urwid.Button("Login/Register", on_press=self.relog)] | ||||||
| 
 | 
 | ||||||
|         time_box = urwid.Text(self.timestring(time(), "time")) |         time_box = urwid.Text(self.timestring(time(), "time")) | ||||||
|         time_edit = urwid.Edit(edit_text=self.prefs["time"]) |         time_edit = Prompt(edit_text=self.prefs["time"]) | ||||||
|         urwid.connect_signal(time_edit, "change", self.live_time_render, (time_box, "time")) |         urwid.connect_signal(time_edit, "change", self.live_time_render, (time_box, "time")) | ||||||
| 
 | 
 | ||||||
|         date_box = urwid.Text(self.timestring(time(), "date")) |         date_box = urwid.Text(self.timestring(time(), "date")) | ||||||
|         date_edit = urwid.Edit(edit_text=self.prefs["date"]) |         date_edit = Prompt(edit_text=self.prefs["date"]) | ||||||
|         urwid.connect_signal(date_edit, "change", self.live_time_render, (date_box, "date")) |         urwid.connect_signal(date_edit, "change", self.live_time_render, (date_box, "date")) | ||||||
| 
 | 
 | ||||||
|         time_stuff = [ |         time_stuff = [ | ||||||
| @ -528,7 +540,10 @@ class App(object): | |||||||
|             date_box, urwid.AttrMap(date_edit, "opt_prompt"), |             date_box, urwid.AttrMap(date_edit, "opt_prompt"), | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         editor_display = urwid.Edit(edit_text=self.prefs["editor"]) |         width_edit = urwid.IntEdit(default=self.prefs["max_text_width"]) | ||||||
|  |         urwid.connect_signal(width_edit, "change", self.edit_width) | ||||||
|  | 
 | ||||||
|  |         editor_display = Prompt(edit_text=self.prefs["editor"]) | ||||||
|         urwid.connect_signal(editor_display, "change", self.set_new_editor, editor_buttons) |         urwid.connect_signal(editor_display, "change", self.set_new_editor, editor_buttons) | ||||||
|         for editor in editors: |         for editor in editors: | ||||||
|             urwid.RadioButton( |             urwid.RadioButton( | ||||||
| @ -564,6 +579,9 @@ class App(object): | |||||||
|                     urwid.Divider(), |                     urwid.Divider(), | ||||||
|                     *time_stuff, |                     *time_stuff, | ||||||
|                     urwid.Divider(), |                     urwid.Divider(), | ||||||
|  |                     urwid.Text(("button", "Max message width:")), | ||||||
|  |                     urwid.AttrMap(width_edit, "opt_prompt"), | ||||||
|  |                     urwid.Divider(), | ||||||
|                     urwid.Text(("button", "Text editor:")), |                     urwid.Text(("button", "Text editor:")), | ||||||
|                     urwid.Text("You can type in your own command or use one of these presets."), |                     urwid.Text("You can type in your own command or use one of these presets."), | ||||||
|                     urwid.Divider(), |                     urwid.Divider(), | ||||||
| @ -587,7 +605,7 @@ class App(object): | |||||||
|             align="center", |             align="center", | ||||||
|             valign="middle", |             valign="middle", | ||||||
|             width=30, |             width=30, | ||||||
|             height=(self.loop.screen_size[1] - 10) |             height=("relative", 75) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -681,8 +699,8 @@ class App(object): | |||||||
|                 self.loop.widget, |                 self.loop.widget, | ||||||
|                 align="center", |                 align="center", | ||||||
|                 valign="middle", |                 valign="middle", | ||||||
|                 width=self.loop.screen_size[0] - 2, |                 width=("relative", 90), | ||||||
|                 height=(self.loop.screen_size[1] - 4)) |                 height=("relative", 80)) | ||||||
| 
 | 
 | ||||||
|         elif self.mode == "thread": |         elif self.mode == "thread": | ||||||
|             self.window_split=True |             self.window_split=True | ||||||
| @ -701,10 +719,87 @@ class App(object): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MessageBody(urwid.Text): | class MessageBody(urwid.Text): | ||||||
|     pass |     def __init__(self, text_objects): | ||||||
|  |         result = [] | ||||||
|  |         last_directive = None | ||||||
|  |         for paragraph in text_objects: | ||||||
|  |             for directive, body in paragraph: | ||||||
|  | 
 | ||||||
|  |                 if directive in colornames: | ||||||
|  |                     color = str(colornames.index(directive)) | ||||||
|  |                     result.append((color, body)) | ||||||
|  | 
 | ||||||
|  |                 elif directive in ["underline", "bold"]: | ||||||
|  |                     result.append((directive, body)) | ||||||
|  | 
 | ||||||
|  |                 elif directive == "linequote": | ||||||
|  |                     if directive != last_directive and result[-1][-1][-1] != "\n": | ||||||
|  |                         result.append(("default", "\n")) | ||||||
|  |                     result.append(("3", "%s\n" % body.strip())) | ||||||
|  | 
 | ||||||
|  |                 elif directive == "quote": | ||||||
|  |                     result.append(("quote", ">>%s" % body)) | ||||||
|  | 
 | ||||||
|  |                 elif directive == "rainbow": | ||||||
|  |                     color = 1 | ||||||
|  |                     for char in body: | ||||||
|  |                         if color == 7: | ||||||
|  |                             color = 1 | ||||||
|  |                         result.append((str(color), char)) | ||||||
|  |                         color += 1 | ||||||
|  | 
 | ||||||
|  |                 else: | ||||||
|  |                     result.append(("default", body)) | ||||||
|  |                 last_directive = directive | ||||||
|  | 
 | ||||||
|  |             result.append("\n\n") | ||||||
|  |         result.pop() | ||||||
|  |         super(MessageBody, self).__init__(result) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FootPrompt(urwid.Edit): | class Prompt(urwid.Edit): | ||||||
|  |     """ | ||||||
|  |     Supports basic bashmacs keybinds. Key casing is | ||||||
|  |     ignored and ctrl/alt are treated the same. Only | ||||||
|  |     character-wise (not word-wise) movements are | ||||||
|  |     implemented. | ||||||
|  |     """ | ||||||
|  |     def keypress(self, size, key): | ||||||
|  |         if not super(Prompt, self).keypress(size, key): | ||||||
|  |             return | ||||||
|  |         elif key[0:4] not in ["meta", "ctrl"]: | ||||||
|  |             return key | ||||||
|  | 
 | ||||||
|  |         column = self.get_cursor_coords((app.loop.screen_size[0],))[0] | ||||||
|  |         text = self.get_edit_text() | ||||||
|  |         key = key[-1].lower() | ||||||
|  | 
 | ||||||
|  |         if key == "u": | ||||||
|  |             self.set_edit_pos(0) | ||||||
|  |             self.set_edit_text(text[column:]) | ||||||
|  | 
 | ||||||
|  |         elif key == "k": | ||||||
|  |             self.set_edit_text(text[:column]) | ||||||
|  | 
 | ||||||
|  |         elif key == "f": | ||||||
|  |             self.keypress(size, "right") | ||||||
|  | 
 | ||||||
|  |         elif key == "b": | ||||||
|  |             self.keypress(size, "left") | ||||||
|  | 
 | ||||||
|  |         elif key == "a": | ||||||
|  |             self.set_edit_pos(0) | ||||||
|  | 
 | ||||||
|  |         elif key == "e": | ||||||
|  |             self.set_edit_pos(len(text)) | ||||||
|  | 
 | ||||||
|  |         elif key == "d": | ||||||
|  |             self.set_edit_text(text[0:column] + text[column+1:]) | ||||||
|  | 
 | ||||||
|  |         return key | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class FootPrompt(Prompt): | ||||||
|     def __init__(self, callback, *callback_args): |     def __init__(self, callback, *callback_args): | ||||||
|         super(FootPrompt, self).__init__() |         super(FootPrompt, self).__init__() | ||||||
|         self.callback = callback |         self.callback = callback | ||||||
| @ -728,6 +823,10 @@ class ExternalEditor(urwid.Terminal): | |||||||
|         self.endpoint = endpoint |         self.endpoint = endpoint | ||||||
|         self.params = params |         self.params = params | ||||||
|         env = os.environ |         env = os.environ | ||||||
|  |         # barring this, programs will happily spit out unicode chars which | ||||||
|  |         # urwid+python3 seem to choke on. This seems to be a bug on urwid's | ||||||
|  |         # behalf. Users who take issue to programs trying to supress unicode | ||||||
|  |         # should use the options menu to switch to Overthrow mode. | ||||||
|         env.update({"LANG": "POSIX"}) |         env.update({"LANG": "POSIX"}) | ||||||
|         command = ["bash", "-c", "{} {}; echo Press any key to kill this window...".format( |         command = ["bash", "-c", "{} {}; echo Press any key to kill this window...".format( | ||||||
|             app.prefs["editor"], self.path)] |             app.prefs["editor"], self.path)] | ||||||
| @ -757,6 +856,16 @@ class ExternalEditor(urwid.Terminal): | |||||||
|         app.switch_editor() |         app.switch_editor() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     def __del__(self): | ||||||
|  |         """ | ||||||
|  |         Make damn sure we scoop up after ourselves here... | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             os.remove(self.path) | ||||||
|  |         except FileNotFoundError: | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class OptionsMenu(urwid.LineBox): | class OptionsMenu(urwid.LineBox): | ||||||
|     def keypress(self, size, key): |     def keypress(self, size, key): | ||||||
|         if key == "esc": |         if key == "esc": | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| from src.exceptions import BBJException, BBJParameterError, BBJUserError | from src.exceptions import BBJException, BBJParameterError, BBJUserError | ||||||
| from src import db, schema | from src import db, schema, formatting | ||||||
| from functools import wraps | from functools import wraps | ||||||
| from uuid import uuid1 | from uuid import uuid1 | ||||||
| import traceback | import traceback | ||||||
| @ -244,6 +244,9 @@ class API(object): | |||||||
|         thread = db.thread_get(database, args["thread_id"]) |         thread = db.thread_get(database, args["thread_id"]) | ||||||
|         cherrypy.thread_data.usermap = \ |         cherrypy.thread_data.usermap = \ | ||||||
|             create_usermap(database, thread["messages"]) |             create_usermap(database, thread["messages"]) | ||||||
|  |         if args.get("format") == "sequential": | ||||||
|  |             formatting.apply_formatting(thread["messages"], | ||||||
|  |                 formatting.sequential_expressions) | ||||||
|         return thread |         return thread | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,34 +3,135 @@ This module is not complete and none of its functions are currently | |||||||
| used elsewhere. Subject to major refactoring. | used elsewhere. Subject to major refactoring. | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from markdown import markdown | test = """ | ||||||
| from html import escape | This is a small paragraph | ||||||
|  | thats divided between a | ||||||
|  | few rows. | ||||||
|  | 
 | ||||||
|  | this opens a few linequotes. | ||||||
|  | >this is a few | ||||||
|  | >rows of | ||||||
|  | >sequential line breaks | ||||||
|  | and this is what follows right after | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | # from markdown import markdown | ||||||
|  | # from html import escape | ||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| #0,   1        2        3        4       5        6 |  | ||||||
| colors = [ | colors = [ | ||||||
|  | #0,   1        2        3        4       5        6 | ||||||
|     "red", "yellow", "green", "blue", "cyan", "magenta" |     "red", "yellow", "green", "blue", "cyan", "magenta" | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| markup = [ | markup = [ | ||||||
|     "bold", "italic", "underline", "strike" |     "bold", "italic", "underline", "linequote", "quote", "rainbow" | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup), | # tokens being [red: this will be red] and [bold: this will be bold] | ||||||
|                     flags=re.DOTALL) | # tokens = re.compile(r"\[(%s): (.+?)]" % "|".join(colors + markup), flags=re.DOTALL) | ||||||
| 
 | 
 | ||||||
|  | # quotes being references to other post_ids, like >>34 or >>0 for OP | ||||||
| quotes = re.compile(">>([0-9]+)") | quotes = re.compile(">>([0-9]+)") | ||||||
| linequotes = re.compile("^(>.+)$", | 
 | ||||||
|     flags=re.MULTILINE) | # linequotes being chan-style greentext, | ||||||
|  | # >like this | ||||||
|  | linequotes = re.compile("^(>.+)$", flags=re.MULTILINE) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def parse_segments(text, sanitize_linequotes=True): | ||||||
|  |     """ | ||||||
|  |     Parse linequotes, quotes, and paragraphs into their appropriate | ||||||
|  |     representations. Paragraphs are represented as separate strings | ||||||
|  |     in the returned list, and quote-types are compiled to their | ||||||
|  |     [bracketed] representations. | ||||||
|  |     """ | ||||||
|  |     result = list() | ||||||
|  |     for paragraph in [p.strip() for p in re.split("\n{2,}", text)]: | ||||||
|  |         pg = str() | ||||||
|  |         for segment in [s.strip() for s in paragraph.split("\n")]: | ||||||
|  |             if not segment: | ||||||
|  |                 continue | ||||||
|  |             segment = quotes.sub(lambda m: "[quote: %s]" % m.group(1), segment) | ||||||
|  |             if segment.startswith(">"): | ||||||
|  |                 if sanitize_linequotes: | ||||||
|  |                     inner = segment.replace("]", "\\]") | ||||||
|  |                 else: | ||||||
|  |                     inner = segment | ||||||
|  |                 segment = "[linequote: %s]" % inner | ||||||
|  |                 # pg = pg[0:-1] | ||||||
|  |                 pg += segment | ||||||
|  |             else: | ||||||
|  |                 pg += segment + " " | ||||||
|  |         result.append(pg.strip()) | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def sequential_expressions(string): | ||||||
|  |     """ | ||||||
|  |     Takes a string, sexpifies it, and returns a list of lists | ||||||
|  |     who contain tuples. Each list of tuples represents a paragraph. | ||||||
|  |     Within each paragraph, [0] is either None or a markup directive, | ||||||
|  |     and [1] is the body of text to which it applies. This representation | ||||||
|  |     is very easy to handle for a client. It semi-supports nesting: | ||||||
|  |     eg, the expression [red: this [blue: is [green: mixed]]] will | ||||||
|  |     return [("red", "this "), ("blue", "is "), ("green", "mixed")], | ||||||
|  |     but this cannot effectively express an input like | ||||||
|  |     [bold: [red: bolded colors.]], in which case the innermost | ||||||
|  |     expression will take precedence. For the input: | ||||||
|  |         "[bold: [red: this] is some shit [green: it cant handle]]" | ||||||
|  |     you get: | ||||||
|  |     [('red', 'this'), ('bold', ' is some shit '), ('green', 'it cant handle')] | ||||||
|  |     """ | ||||||
|  |     # abandon all hope ye who enter here | ||||||
|  |     directives = colors + markup | ||||||
|  |     result = list() | ||||||
|  |     for paragraph in parse_segments(string): | ||||||
|  |         stack = [[None, str()]] | ||||||
|  |         skip_iters = [] | ||||||
|  |         nest = [None] | ||||||
|  |         escaped = False | ||||||
|  |         for index, char in enumerate(paragraph): | ||||||
|  |             if skip_iters: | ||||||
|  |                 skip_iters.pop() | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|  |             if not escaped and char == "[": | ||||||
|  |                 directive = paragraph[index+1:paragraph.find(": ", index+1)] | ||||||
|  |                 open_p = directive in directives | ||||||
|  |             else: open_p = False | ||||||
|  |             clsd_p = not escaped and nest[-1] != None and char == "]" | ||||||
|  | 
 | ||||||
|  |             # dont splice other directives into linequotes: that is far | ||||||
|  |             # too confusing for the client to determine where to put line | ||||||
|  |             # breaks | ||||||
|  |             if open_p and nest[-1] != "linequote": | ||||||
|  |                 stack.append([directive, str()]) | ||||||
|  |                 nest.append(directive) | ||||||
|  |                 [skip_iters.append(x) for x in range(len(directive)+2)] | ||||||
|  | 
 | ||||||
|  |             elif clsd_p: | ||||||
|  |                 nest.pop() | ||||||
|  |                 stack.append([nest[-1], str()]) | ||||||
|  | 
 | ||||||
|  |             else: | ||||||
|  |                 escaped = char == "\\" | ||||||
|  |                 if not (escaped and paragraph[index+1] in "[]"): | ||||||
|  |                     stack[-1][1] += char | ||||||
|  |         # filter out unused stacks, eg ["red", ""] | ||||||
|  |         result.append([(directive, body) for directive, body in stack if body]) | ||||||
|  |     return result | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def apply_formatting(msg_obj, formatter): | def apply_formatting(msg_obj, formatter): | ||||||
|     """ |     """ | ||||||
|     Receives a messages object from a thread and returns it with |     Receives a messages object from a thread and returns it with | ||||||
|     all the message bodies passed through FORMATTER. |     all the message bodies passed through FORMATTER. Not all | ||||||
|  |     formatting functions have to return a string. Refer to the | ||||||
|  |     documentation for each formatter. | ||||||
|     """ |     """ | ||||||
|     for x in range(len(msg_obj)): |     for x, obj in enumerate(msg_obj): | ||||||
|         msg_obj[x]["body"] = formatter(msg_obj[x]["body"]) |         msg_obj[x]["body"] = formatter(obj["body"]) | ||||||
|     return msg_obj |     return msg_obj | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user