Start splitting up the script into different files.
This commit is contained in:
parent
704cadc5c0
commit
62adae5aa3
3 changed files with 885 additions and 869 deletions
0
matrix/__init__.py
Normal file
0
matrix/__init__.py
Normal file
845
matrix/colors.py
Normal file
845
matrix/colors.py
Normal file
|
@ -0,0 +1,845 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import webcolors
|
||||
|
||||
try:
|
||||
from HTMLParser import HTMLParser
|
||||
except ImportError:
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
FormattedString = namedtuple(
|
||||
'FormattedString',
|
||||
['text', 'attributes']
|
||||
)
|
||||
|
||||
|
||||
# TODO this should be a typed dict.
|
||||
DEFAULT_ATRIBUTES = {
|
||||
"bold": False,
|
||||
"italic": False,
|
||||
"underline": False,
|
||||
"strikethrough": False,
|
||||
"quote": False,
|
||||
"fgcolor": None,
|
||||
"bgcolor": None
|
||||
}
|
||||
|
||||
|
||||
class MatrixHtmlParser(HTMLParser):
|
||||
# TODO bg color
|
||||
# TODO bullets
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self)
|
||||
self.text = "" # type: str
|
||||
self.substrings = [] # type: List[FormattedString]
|
||||
self.attributes = DEFAULT_ATRIBUTES.copy()
|
||||
|
||||
def _toggle_attribute(self, attribute):
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes[attribute] = not self.attributes[attribute]
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "strong":
|
||||
self._toggle_attribute("bold")
|
||||
elif tag == "em":
|
||||
self._toggle_attribute("italic")
|
||||
elif tag == "u":
|
||||
self._toggle_attribute("underline")
|
||||
elif tag == "del":
|
||||
self._toggle_attribute("strikethrough")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "font":
|
||||
for key, value in attrs:
|
||||
if key == "color":
|
||||
color = color_html_to_weechat(value)
|
||||
|
||||
if not color:
|
||||
continue
|
||||
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes["fgcolor"] = color
|
||||
else:
|
||||
# W.prnt("", "Unhandled tag {t}".format(t=tag))
|
||||
pass
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "strong":
|
||||
self._toggle_attribute("bold")
|
||||
elif tag == "em":
|
||||
self._toggle_attribute("italic")
|
||||
elif tag == "u":
|
||||
self._toggle_attribute("underline")
|
||||
elif tag == "del":
|
||||
self._toggle_attribute("strikethrough")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "font":
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes["fgcolor"] = None
|
||||
else:
|
||||
pass
|
||||
|
||||
def handle_data(self, data):
|
||||
self.text = self.text + data
|
||||
|
||||
def get_substrings(self):
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
|
||||
return self.substrings
|
||||
|
||||
|
||||
def color_line_to_weechat(color_string):
|
||||
# type: (str) -> str
|
||||
line_colors = {
|
||||
"0": "white",
|
||||
"1": "black",
|
||||
"2": "blue",
|
||||
"3": "green",
|
||||
"4": "lightred",
|
||||
"5": "red",
|
||||
"6": "magenta",
|
||||
"7": "brown",
|
||||
"8": "yellow",
|
||||
"9": "lightgreen",
|
||||
"10": "cyan",
|
||||
"11": "lightcyan",
|
||||
"12": "lightblue",
|
||||
"13": "lightmagenta",
|
||||
"14": "darkgray",
|
||||
"15": "gray",
|
||||
"16": "52",
|
||||
"17": "94",
|
||||
"18": "100",
|
||||
"19": "58",
|
||||
"20": "22",
|
||||
"21": "29",
|
||||
"22": "23",
|
||||
"23": "24",
|
||||
"24": "17",
|
||||
"25": "54",
|
||||
"26": "53",
|
||||
"27": "89",
|
||||
"28": "88",
|
||||
"29": "130",
|
||||
"30": "142",
|
||||
"31": "64",
|
||||
"32": "28",
|
||||
"33": "35",
|
||||
"34": "30",
|
||||
"35": "25",
|
||||
"36": "18",
|
||||
"37": "91",
|
||||
"38": "90",
|
||||
"39": "125",
|
||||
"40": "124",
|
||||
"41": "166",
|
||||
"42": "184",
|
||||
"43": "106",
|
||||
"44": "34",
|
||||
"45": "49",
|
||||
"46": "37",
|
||||
"47": "33",
|
||||
"48": "19",
|
||||
"49": "129",
|
||||
"50": "127",
|
||||
"51": "161",
|
||||
"52": "196",
|
||||
"53": "208",
|
||||
"54": "226",
|
||||
"55": "154",
|
||||
"56": "46",
|
||||
"57": "86",
|
||||
"58": "51",
|
||||
"59": "75",
|
||||
"60": "21",
|
||||
"61": "171",
|
||||
"62": "201",
|
||||
"63": "198",
|
||||
"64": "203",
|
||||
"65": "215",
|
||||
"66": "227",
|
||||
"67": "191",
|
||||
"68": "83",
|
||||
"69": "122",
|
||||
"70": "87",
|
||||
"71": "111",
|
||||
"72": "63",
|
||||
"73": "177",
|
||||
"74": "207",
|
||||
"75": "205",
|
||||
"76": "217",
|
||||
"77": "223",
|
||||
"78": "229",
|
||||
"79": "193",
|
||||
"80": "157",
|
||||
"81": "158",
|
||||
"82": "159",
|
||||
"83": "153",
|
||||
"84": "147",
|
||||
"85": "183",
|
||||
"86": "219",
|
||||
"87": "212",
|
||||
"88": "16",
|
||||
"89": "233",
|
||||
"90": "235",
|
||||
"91": "237",
|
||||
"92": "239",
|
||||
"93": "241",
|
||||
"94": "244",
|
||||
"95": "247",
|
||||
"96": "250",
|
||||
"97": "254",
|
||||
"98": "231",
|
||||
"99": "default"
|
||||
}
|
||||
|
||||
assert color_string in line_colors
|
||||
|
||||
return line_colors[color_string]
|
||||
|
||||
|
||||
# The functions colour_dist_sq(), colour_to_6cube(), and colour_find_rgb
|
||||
# are python ports of the same named functions from the tmux
|
||||
# source, they are under the copyright of Nicholas Marriott, and Avi Halachmi
|
||||
# under the ISC license.
|
||||
# More info: https://github.com/tmux/tmux/blob/master/colour.c
|
||||
|
||||
def colour_dist_sq(R, G, B, r, g, b):
|
||||
# pylint: disable=invalid-name,too-many-arguments
|
||||
# type: (int, int, int, int, int, int) -> int
|
||||
return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
|
||||
|
||||
|
||||
def colour_to_6cube(v):
|
||||
# pylint: disable=invalid-name
|
||||
# type: (int) -> int
|
||||
if v < 48:
|
||||
return 0
|
||||
if v < 114:
|
||||
return 1
|
||||
return (v - 35) // 40
|
||||
|
||||
|
||||
def colour_find_rgb(r, g, b):
|
||||
# type: (int, int, int) -> int
|
||||
"""Convert an RGB triplet to the xterm(1) 256 colour palette.
|
||||
|
||||
xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255).
|
||||
We map our RGB colour to the closest in the cube, also work out the
|
||||
closest grey, and use the nearest of the two.
|
||||
|
||||
Note that the xterm has much lower resolution for darker colours (they
|
||||
are not evenly spread out), so our 6 levels are not evenly spread: 0x0,
|
||||
0x5f (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are
|
||||
more evenly spread (8, 18, 28 ... 238).
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
||||
|
||||
# Map RGB to 6x6x6 cube.
|
||||
qr = colour_to_6cube(r)
|
||||
qg = colour_to_6cube(g)
|
||||
qb = colour_to_6cube(b)
|
||||
|
||||
cr = q2c[qr]
|
||||
cg = q2c[qg]
|
||||
cb = q2c[qb]
|
||||
|
||||
# If we have hit the colour exactly, return early.
|
||||
if (cr == r and cg == g and cb == b):
|
||||
return 16 + (36 * qr) + (6 * qg) + qb
|
||||
|
||||
# Work out the closest grey (average of RGB).
|
||||
grey_avg = (r + g + b) // 3
|
||||
|
||||
if grey_avg > 238:
|
||||
grey_idx = 23
|
||||
else:
|
||||
grey_idx = (grey_avg - 3) // 10
|
||||
|
||||
grey = 8 + (10 * grey_idx)
|
||||
|
||||
# Is grey or 6x6x6 colour closest?
|
||||
d = colour_dist_sq(cr, cg, cb, r, g, b)
|
||||
|
||||
if colour_dist_sq(grey, grey, grey, r, g, b) < d:
|
||||
idx = 232 + grey_idx
|
||||
else:
|
||||
idx = 16 + (36 * qr) + (6 * qg) + qb
|
||||
|
||||
return idx
|
||||
|
||||
|
||||
def color_html_to_weechat(color):
|
||||
# type: (str) -> str
|
||||
first_16 = {
|
||||
(0, 0, 0): "black", # 0
|
||||
(128, 0, 0): "red", # 1
|
||||
(0, 128, 0): "green", # 2
|
||||
(128, 128, 0): "brown", # 3
|
||||
(0, 0, 128): "blue", # 4
|
||||
(128, 0, 128): "magenta", # 5
|
||||
(0, 128, 128): "cyan", # 6
|
||||
(192, 192, 192): "default", # 7
|
||||
(128, 128, 128): "gray", # 8
|
||||
(255, 0, 0): "lightred", # 9
|
||||
(0, 255, 0): "lightgreen", # 11
|
||||
(255, 255, 0): "yellow", # 12
|
||||
(0, 0, 255): "lightblue", # 13
|
||||
(255, 0, 255): "lightmagenta", # 14
|
||||
(0, 255, 255): "lightcyan", # 15
|
||||
}
|
||||
|
||||
try:
|
||||
rgb_color = webcolors.html5_parse_legacy_color(color)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if rgb_color in first_16:
|
||||
return first_16[rgb_color]
|
||||
|
||||
return str(colour_find_rgb(*rgb_color))
|
||||
|
||||
|
||||
def color_weechat_to_html(color):
|
||||
first_16 = {
|
||||
"black": "black", # 0
|
||||
"red": "maroon", # 1
|
||||
"green": "green", # 2
|
||||
"brown": "olive", # 3
|
||||
"blue": "navy", # 4
|
||||
"magenta": "purple", # 5
|
||||
"cyan": "teal", # 6
|
||||
"default": "silver", # 7
|
||||
"gray": "grey", # 8
|
||||
"lightred": "red", # 9
|
||||
"lightgreen": "lime", # 11
|
||||
"yellow": "yellow", # 12
|
||||
"lightblue": "fuchsia", # 13
|
||||
"lightmagenta": "aqua", # 14
|
||||
"lightcyan": "white", # 15
|
||||
}
|
||||
|
||||
hex_colors = {
|
||||
"0": "#000000",
|
||||
"1": "#800000",
|
||||
"2": "#008000",
|
||||
"3": "#808000",
|
||||
"4": "#000080",
|
||||
"5": "#800080",
|
||||
"6": "#008080",
|
||||
"7": "#c0c0c0",
|
||||
"8": "#808080",
|
||||
"9": "#ff0000",
|
||||
"10": "#00ff00",
|
||||
"11": "#ffff00",
|
||||
"12": "#0000ff",
|
||||
"13": "#ff00ff",
|
||||
"14": "#00ffff",
|
||||
"15": "#ffffff",
|
||||
"16": "#000000",
|
||||
"17": "#00005f",
|
||||
"18": "#000087",
|
||||
"19": "#0000af",
|
||||
"20": "#0000d7",
|
||||
"21": "#0000ff",
|
||||
"22": "#005f00",
|
||||
"23": "#005f5f",
|
||||
"24": "#005f87",
|
||||
"25": "#005faf",
|
||||
"26": "#005fd7",
|
||||
"27": "#005fff",
|
||||
"28": "#008700",
|
||||
"29": "#00875f",
|
||||
"30": "#008787",
|
||||
"31": "#0087af",
|
||||
"32": "#0087d7",
|
||||
"33": "#0087ff",
|
||||
"34": "#00af00",
|
||||
"35": "#00af5f",
|
||||
"36": "#00af87",
|
||||
"37": "#00afaf",
|
||||
"38": "#00afd7",
|
||||
"39": "#00afff",
|
||||
"40": "#00d700",
|
||||
"41": "#00d75f",
|
||||
"42": "#00d787",
|
||||
"43": "#00d7af",
|
||||
"44": "#00d7d7",
|
||||
"45": "#00d7ff",
|
||||
"46": "#00ff00",
|
||||
"47": "#00ff5f",
|
||||
"48": "#00ff87",
|
||||
"49": "#00ffaf",
|
||||
"50": "#00ffd7",
|
||||
"51": "#00ffff",
|
||||
"52": "#5f0000",
|
||||
"53": "#5f005f",
|
||||
"54": "#5f0087",
|
||||
"55": "#5f00af",
|
||||
"56": "#5f00d7",
|
||||
"57": "#5f00ff",
|
||||
"58": "#5f5f00",
|
||||
"59": "#5f5f5f",
|
||||
"60": "#5f5f87",
|
||||
"61": "#5f5faf",
|
||||
"62": "#5f5fd7",
|
||||
"63": "#5f5fff",
|
||||
"64": "#5f8700",
|
||||
"65": "#5f875f",
|
||||
"66": "#5f8787",
|
||||
"67": "#5f87af",
|
||||
"68": "#5f87d7",
|
||||
"69": "#5f87ff",
|
||||
"70": "#5faf00",
|
||||
"71": "#5faf5f",
|
||||
"72": "#5faf87",
|
||||
"73": "#5fafaf",
|
||||
"74": "#5fafd7",
|
||||
"75": "#5fafff",
|
||||
"76": "#5fd700",
|
||||
"77": "#5fd75f",
|
||||
"78": "#5fd787",
|
||||
"79": "#5fd7af",
|
||||
"80": "#5fd7d7",
|
||||
"81": "#5fd7ff",
|
||||
"82": "#5fff00",
|
||||
"83": "#5fff5f",
|
||||
"84": "#5fff87",
|
||||
"85": "#5fffaf",
|
||||
"86": "#5fffd7",
|
||||
"87": "#5fffff",
|
||||
"88": "#870000",
|
||||
"89": "#87005f",
|
||||
"90": "#870087",
|
||||
"91": "#8700af",
|
||||
"92": "#8700d7",
|
||||
"93": "#8700ff",
|
||||
"94": "#875f00",
|
||||
"95": "#875f5f",
|
||||
"96": "#875f87",
|
||||
"97": "#875faf",
|
||||
"98": "#875fd7",
|
||||
"99": "#875fff",
|
||||
"100": "#878700",
|
||||
"101": "#87875f",
|
||||
"102": "#878787",
|
||||
"103": "#8787af",
|
||||
"104": "#8787d7",
|
||||
"105": "#8787ff",
|
||||
"106": "#87af00",
|
||||
"107": "#87af5f",
|
||||
"108": "#87af87",
|
||||
"109": "#87afaf",
|
||||
"110": "#87afd7",
|
||||
"111": "#87afff",
|
||||
"112": "#87d700",
|
||||
"113": "#87d75f",
|
||||
"114": "#87d787",
|
||||
"115": "#87d7af",
|
||||
"116": "#87d7d7",
|
||||
"117": "#87d7ff",
|
||||
"118": "#87ff00",
|
||||
"119": "#87ff5f",
|
||||
"120": "#87ff87",
|
||||
"121": "#87ffaf",
|
||||
"122": "#87ffd7",
|
||||
"123": "#87ffff",
|
||||
"124": "#af0000",
|
||||
"125": "#af005f",
|
||||
"126": "#af0087",
|
||||
"127": "#af00af",
|
||||
"128": "#af00d7",
|
||||
"129": "#af00ff",
|
||||
"130": "#af5f00",
|
||||
"131": "#af5f5f",
|
||||
"132": "#af5f87",
|
||||
"133": "#af5faf",
|
||||
"134": "#af5fd7",
|
||||
"135": "#af5fff",
|
||||
"136": "#af8700",
|
||||
"137": "#af875f",
|
||||
"138": "#af8787",
|
||||
"139": "#af87af",
|
||||
"140": "#af87d7",
|
||||
"141": "#af87ff",
|
||||
"142": "#afaf00",
|
||||
"143": "#afaf5f",
|
||||
"144": "#afaf87",
|
||||
"145": "#afafaf",
|
||||
"146": "#afafd7",
|
||||
"147": "#afafff",
|
||||
"148": "#afd700",
|
||||
"149": "#afd75f",
|
||||
"150": "#afd787",
|
||||
"151": "#afd7af",
|
||||
"152": "#afd7d7",
|
||||
"153": "#afd7ff",
|
||||
"154": "#afff00",
|
||||
"155": "#afff5f",
|
||||
"156": "#afff87",
|
||||
"157": "#afffaf",
|
||||
"158": "#afffd7",
|
||||
"159": "#afffff",
|
||||
"160": "#d70000",
|
||||
"161": "#d7005f",
|
||||
"162": "#d70087",
|
||||
"163": "#d700af",
|
||||
"164": "#d700d7",
|
||||
"165": "#d700ff",
|
||||
"166": "#d75f00",
|
||||
"167": "#d75f5f",
|
||||
"168": "#d75f87",
|
||||
"169": "#d75faf",
|
||||
"170": "#d75fd7",
|
||||
"171": "#d75fff",
|
||||
"172": "#d78700",
|
||||
"173": "#d7875f",
|
||||
"174": "#d78787",
|
||||
"175": "#d787af",
|
||||
"176": "#d787d7",
|
||||
"177": "#d787ff",
|
||||
"178": "#d7af00",
|
||||
"179": "#d7af5f",
|
||||
"180": "#d7af87",
|
||||
"181": "#d7afaf",
|
||||
"182": "#d7afd7",
|
||||
"183": "#d7afff",
|
||||
"184": "#d7d700",
|
||||
"185": "#d7d75f",
|
||||
"186": "#d7d787",
|
||||
"187": "#d7d7af",
|
||||
"188": "#d7d7d7",
|
||||
"189": "#d7d7ff",
|
||||
"190": "#d7ff00",
|
||||
"191": "#d7ff5f",
|
||||
"192": "#d7ff87",
|
||||
"193": "#d7ffaf",
|
||||
"194": "#d7ffd7",
|
||||
"195": "#d7ffff",
|
||||
"196": "#ff0000",
|
||||
"197": "#ff005f",
|
||||
"198": "#ff0087",
|
||||
"199": "#ff00af",
|
||||
"200": "#ff00d7",
|
||||
"201": "#ff00ff",
|
||||
"202": "#ff5f00",
|
||||
"203": "#ff5f5f",
|
||||
"204": "#ff5f87",
|
||||
"205": "#ff5faf",
|
||||
"206": "#ff5fd7",
|
||||
"207": "#ff5fff",
|
||||
"208": "#ff8700",
|
||||
"209": "#ff875f",
|
||||
"210": "#ff8787",
|
||||
"211": "#ff87af",
|
||||
"212": "#ff87d7",
|
||||
"213": "#ff87ff",
|
||||
"214": "#ffaf00",
|
||||
"215": "#ffaf5f",
|
||||
"216": "#ffaf87",
|
||||
"217": "#ffafaf",
|
||||
"218": "#ffafd7",
|
||||
"219": "#ffafff",
|
||||
"220": "#ffd700",
|
||||
"221": "#ffd75f",
|
||||
"222": "#ffd787",
|
||||
"223": "#ffd7af",
|
||||
"224": "#ffd7d7",
|
||||
"225": "#ffd7ff",
|
||||
"226": "#ffff00",
|
||||
"227": "#ffff5f",
|
||||
"228": "#ffff87",
|
||||
"229": "#ffffaf",
|
||||
"230": "#ffffd7",
|
||||
"231": "#ffffff",
|
||||
"232": "#080808",
|
||||
"233": "#121212",
|
||||
"234": "#1c1c1c",
|
||||
"235": "#262626",
|
||||
"236": "#303030",
|
||||
"237": "#3a3a3a",
|
||||
"238": "#444444",
|
||||
"239": "#4e4e4e",
|
||||
"240": "#585858",
|
||||
"241": "#626262",
|
||||
"242": "#6c6c6c",
|
||||
"243": "#767676",
|
||||
"244": "#808080",
|
||||
"245": "#8a8a8a",
|
||||
"246": "#949494",
|
||||
"247": "#9e9e9e",
|
||||
"248": "#a8a8a8",
|
||||
"249": "#b2b2b2",
|
||||
"250": "#bcbcbc",
|
||||
"251": "#c6c6c6",
|
||||
"252": "#d0d0d0",
|
||||
"253": "#dadada",
|
||||
"254": "#e4e4e4",
|
||||
"255": "#eeeeee"
|
||||
}
|
||||
|
||||
if color in first_16:
|
||||
return first_16[color]
|
||||
|
||||
hex_color = hex_colors[color]
|
||||
|
||||
try:
|
||||
return webcolors.hex_to_name(hex_color)
|
||||
except ValueError:
|
||||
return hex_color
|
||||
|
||||
|
||||
# TODO reverse video
|
||||
def parse_input_line(line):
|
||||
# type: (str) -> List[FormattedString]
|
||||
"""Parses the weechat input line and produces formatted strings that can be
|
||||
later converted to HTML or to a string for weechat's print functions
|
||||
"""
|
||||
text = "" # type: str
|
||||
substrings = [] # type: List[FormattedString]
|
||||
attributes = DEFAULT_ATRIBUTES.copy()
|
||||
|
||||
i = 0
|
||||
while i < len(line):
|
||||
# Bold
|
||||
if line[i] == "\x02":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["bold"] = not attributes["bold"]
|
||||
i = i + 1
|
||||
|
||||
# Color
|
||||
elif line[i] == "\x03":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
i = i + 1
|
||||
|
||||
# check if it's a valid color, add it to the attributes
|
||||
if line[i].isdigit():
|
||||
color_string = line[i]
|
||||
i = i + 1
|
||||
|
||||
if line[i].isdigit():
|
||||
if color_string == "0":
|
||||
color_string = line[i]
|
||||
else:
|
||||
color_string = color_string + line[i]
|
||||
i = i + 1
|
||||
|
||||
attributes["fgcolor"] = color_line_to_weechat(color_string)
|
||||
else:
|
||||
attributes["fgcolor"] = None
|
||||
|
||||
# check if we have a background color
|
||||
if line[i] == "," and line[i+1].isdigit():
|
||||
color_string = line[i+1]
|
||||
i = i + 2
|
||||
|
||||
if line[i].isdigit():
|
||||
if color_string == "0":
|
||||
color_string = line[i]
|
||||
else:
|
||||
color_string = color_string + line[i]
|
||||
i = i + 1
|
||||
|
||||
attributes["bgcolor"] = color_line_to_weechat(color_string)
|
||||
else:
|
||||
attributes["bgcolor"] = None
|
||||
# Reset
|
||||
elif line[i] == "\x0F":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
# Reset all the attributes
|
||||
attributes = DEFAULT_ATRIBUTES.copy()
|
||||
i = i + 1
|
||||
# Italic
|
||||
elif line[i] == "\x1D":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["italic"] = not attributes["italic"]
|
||||
i = i + 1
|
||||
|
||||
# Underline
|
||||
elif line[i] == "\x1F":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["underline"] = not attributes["underline"]
|
||||
i = i + 1
|
||||
|
||||
# Normal text
|
||||
else:
|
||||
text = text + line[i]
|
||||
i = i + 1
|
||||
|
||||
substrings.append(FormattedString(text, attributes))
|
||||
return substrings
|
||||
|
||||
|
||||
def formatted(strings):
|
||||
for string in strings:
|
||||
if string.attributes != DEFAULT_ATRIBUTES:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def formatted_to_html(strings):
|
||||
# type: (List[FormattedString]) -> str
|
||||
# TODO BG COLOR
|
||||
def add_attribute(string, name, value):
|
||||
if name == "bold" and value:
|
||||
return "{bold_on}{text}{bold_off}".format(
|
||||
bold_on="<strong>",
|
||||
text=string,
|
||||
bold_off="</strong>")
|
||||
elif name == "italic" and value:
|
||||
return "{italic_on}{text}{italic_off}".format(
|
||||
italic_on="<em>",
|
||||
text=string,
|
||||
italic_off="</em>")
|
||||
elif name == "underline" and value:
|
||||
return "{underline_on}{text}{underline_off}".format(
|
||||
underline_on="<u>",
|
||||
text=string,
|
||||
underline_off="</u>")
|
||||
elif name == "strikethrough" and value:
|
||||
return "{strike_on}{text}{strike_off}".format(
|
||||
strike_on="<del>",
|
||||
text=string,
|
||||
strike_off="</del>")
|
||||
elif name == "quote" and value:
|
||||
return "{quote_on}{text}{quote_off}".format(
|
||||
quote_on="<blockquote>",
|
||||
text=string,
|
||||
quote_off="</blockquote>")
|
||||
elif name == "fgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on="<font color={color}>".format(
|
||||
color=color_weechat_to_html(value)
|
||||
),
|
||||
text=string,
|
||||
color_off="</font>")
|
||||
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = add_attribute(text, key, value)
|
||||
return text
|
||||
|
||||
html_string = map(format_string, strings)
|
||||
return "".join(html_string)
|
||||
|
||||
|
||||
# TODO do we want at least some formating using unicode
|
||||
# (strikethrough, quotes)?
|
||||
def formatted_to_plain(strings):
|
||||
# type: (List[FormattedString]) -> str
|
||||
def strip_atribute(string, _, __):
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = strip_atribute(text, key, value)
|
||||
return text
|
||||
|
||||
plain_string = map(format_string, strings)
|
||||
return "".join(plain_string)
|
||||
|
||||
|
||||
def html_to_formatted(html):
|
||||
parser = MatrixHtmlParser()
|
||||
parser.feed(html)
|
||||
return parser.get_substrings()
|
||||
|
||||
|
||||
def string_strikethrough(string):
|
||||
return "".join(["{}\u0336".format(c) for c in string])
|
||||
|
||||
|
||||
def formatted_to_weechat(W, strings):
|
||||
# type: (weechat, List[colors.FormattedString]) -> str
|
||||
# TODO BG COLOR
|
||||
def add_attribute(string, name, value):
|
||||
if name == "bold" and value:
|
||||
return "{bold_on}{text}{bold_off}".format(
|
||||
bold_on=W.color("bold"),
|
||||
text=string,
|
||||
bold_off=W.color("-bold"))
|
||||
|
||||
elif name == "italic" and value:
|
||||
return "{italic_on}{text}{italic_off}".format(
|
||||
italic_on=W.color("italic"),
|
||||
text=string,
|
||||
italic_off=W.color("-italic"))
|
||||
|
||||
elif name == "underline" and value:
|
||||
return "{underline_on}{text}{underline_off}".format(
|
||||
underline_on=W.color("underline"),
|
||||
text=string,
|
||||
underline_off=W.color("-underline"))
|
||||
|
||||
elif name == "strikethrough" and value:
|
||||
return string_strikethrough(string)
|
||||
|
||||
elif name == "quote" and value:
|
||||
return "“{text}”".format(text=string)
|
||||
|
||||
elif name == "fgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on=W.color(value),
|
||||
text=string,
|
||||
color_off=W.color("resetcolor"))
|
||||
|
||||
elif name == "bgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on=W.color("," + value),
|
||||
text=string,
|
||||
color_off=W.color("resetcolor"))
|
||||
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = add_attribute(text, key, value)
|
||||
return text
|
||||
|
||||
weechat_strings = map(format_string, strings)
|
||||
return "".join(weechat_strings)
|
|
@ -22,13 +22,8 @@ from functools import wraps
|
|||
# pylint: disable=unused-import
|
||||
from typing import (List, Set, Dict, Tuple, Text, Optional, AnyStr, Deque, Any)
|
||||
|
||||
import webcolors
|
||||
from http_parser.pyparser import HttpParser
|
||||
|
||||
try:
|
||||
from HTMLParser import HTMLParser
|
||||
except ImportError:
|
||||
from html.parser import HTMLParser
|
||||
from matrix import colors
|
||||
|
||||
|
||||
# pylint: disable=import-error
|
||||
|
@ -275,14 +270,14 @@ class MatrixMessage:
|
|||
):
|
||||
# type: (...) -> None
|
||||
# pylint: disable=dangerous-default-value
|
||||
self.type = message_type # MessageType
|
||||
self.request = None # HttpRequest
|
||||
self.response = None # HttpResponse
|
||||
self.extra_data = extra_data # Dict[str, Any]
|
||||
self.type = message_type # type: MessageType
|
||||
self.request = None # type: HttpRequest
|
||||
self.response = None # type: HttpResponse
|
||||
self.extra_data = extra_data # type: Dict[str, Any]
|
||||
|
||||
self.creation_time = time.time()
|
||||
self.send_time = None
|
||||
self.receive_time = None
|
||||
self.creation_time = time.time() # type: float
|
||||
self.send_time = None # type: float
|
||||
self.receive_time = None # type: float
|
||||
|
||||
if message_type == MessageType.LOGIN:
|
||||
path = ("{api}/login").format(api=MATRIX_API_PATH)
|
||||
|
@ -596,833 +591,6 @@ class MatrixServer:
|
|||
"", "server_config_change_cb", self.name, "", "")
|
||||
|
||||
|
||||
FormattedString = namedtuple(
|
||||
'FormattedString',
|
||||
['text', 'attributes']
|
||||
)
|
||||
|
||||
DEFAULT_ATRIBUTES = {
|
||||
"bold": False,
|
||||
"italic": False,
|
||||
"underline": False,
|
||||
"strikethrough": False,
|
||||
"quote": False,
|
||||
"fgcolor": None,
|
||||
"bgcolor": None
|
||||
}
|
||||
|
||||
|
||||
def color_line_to_weechat(color_string):
|
||||
# type: (str) -> str
|
||||
line_colors = {
|
||||
"0": "white",
|
||||
"1": "black",
|
||||
"2": "blue",
|
||||
"3": "green",
|
||||
"4": "lightred",
|
||||
"5": "red",
|
||||
"6": "magenta",
|
||||
"7": "brown",
|
||||
"8": "yellow",
|
||||
"9": "lightgreen",
|
||||
"10": "cyan",
|
||||
"11": "lightcyan",
|
||||
"12": "lightblue",
|
||||
"13": "lightmagenta",
|
||||
"14": "darkgray",
|
||||
"15": "gray",
|
||||
"16": "52",
|
||||
"17": "94",
|
||||
"18": "100",
|
||||
"19": "58",
|
||||
"20": "22",
|
||||
"21": "29",
|
||||
"22": "23",
|
||||
"23": "24",
|
||||
"24": "17",
|
||||
"25": "54",
|
||||
"26": "53",
|
||||
"27": "89",
|
||||
"28": "88",
|
||||
"29": "130",
|
||||
"30": "142",
|
||||
"31": "64",
|
||||
"32": "28",
|
||||
"33": "35",
|
||||
"34": "30",
|
||||
"35": "25",
|
||||
"36": "18",
|
||||
"37": "91",
|
||||
"38": "90",
|
||||
"39": "125",
|
||||
"40": "124",
|
||||
"41": "166",
|
||||
"42": "184",
|
||||
"43": "106",
|
||||
"44": "34",
|
||||
"45": "49",
|
||||
"46": "37",
|
||||
"47": "33",
|
||||
"48": "19",
|
||||
"49": "129",
|
||||
"50": "127",
|
||||
"51": "161",
|
||||
"52": "196",
|
||||
"53": "208",
|
||||
"54": "226",
|
||||
"55": "154",
|
||||
"56": "46",
|
||||
"57": "86",
|
||||
"58": "51",
|
||||
"59": "75",
|
||||
"60": "21",
|
||||
"61": "171",
|
||||
"62": "201",
|
||||
"63": "198",
|
||||
"64": "203",
|
||||
"65": "215",
|
||||
"66": "227",
|
||||
"67": "191",
|
||||
"68": "83",
|
||||
"69": "122",
|
||||
"70": "87",
|
||||
"71": "111",
|
||||
"72": "63",
|
||||
"73": "177",
|
||||
"74": "207",
|
||||
"75": "205",
|
||||
"76": "217",
|
||||
"77": "223",
|
||||
"78": "229",
|
||||
"79": "193",
|
||||
"80": "157",
|
||||
"81": "158",
|
||||
"82": "159",
|
||||
"83": "153",
|
||||
"84": "147",
|
||||
"85": "183",
|
||||
"86": "219",
|
||||
"87": "212",
|
||||
"88": "16",
|
||||
"89": "233",
|
||||
"90": "235",
|
||||
"91": "237",
|
||||
"92": "239",
|
||||
"93": "241",
|
||||
"94": "244",
|
||||
"95": "247",
|
||||
"96": "250",
|
||||
"97": "254",
|
||||
"98": "231",
|
||||
"99": "default"
|
||||
}
|
||||
|
||||
assert color_string in line_colors
|
||||
|
||||
return line_colors[color_string]
|
||||
|
||||
|
||||
# The functions colour_dist_sq(), colour_to_6cube(), and colour_find_rgb
|
||||
# are python ports of the same named functions from the tmux
|
||||
# source, they are under the copyright of Nicholas Marriott, and Avi Halachmi
|
||||
# under the ISC license.
|
||||
# More info: https://github.com/tmux/tmux/blob/master/colour.c
|
||||
|
||||
def colour_dist_sq(R, G, B, r, g, b):
|
||||
# pylint: disable=invalid-name,too-many-arguments
|
||||
# type: (int, int, int, int, int, int) -> int
|
||||
return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
|
||||
|
||||
|
||||
def colour_to_6cube(v):
|
||||
# pylint: disable=invalid-name
|
||||
# type: (int) -> int
|
||||
if v < 48:
|
||||
return 0
|
||||
if v < 114:
|
||||
return 1
|
||||
return (v - 35) // 40
|
||||
|
||||
|
||||
def colour_find_rgb(r, g, b):
|
||||
"""Convert an RGB triplet to the xterm(1) 256 colour palette.
|
||||
|
||||
xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255).
|
||||
We map our RGB colour to the closest in the cube, also work out the
|
||||
closest grey, and use the nearest of the two.
|
||||
|
||||
Note that the xterm has much lower resolution for darker colours (they
|
||||
are not evenly spread out), so our 6 levels are not evenly spread: 0x0,
|
||||
0x5f (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are
|
||||
more evenly spread (8, 18, 28 ... 238).
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
# type: (int, int, int) -> int
|
||||
q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
||||
|
||||
# Map RGB to 6x6x6 cube.
|
||||
qr = colour_to_6cube(r)
|
||||
qg = colour_to_6cube(g)
|
||||
qb = colour_to_6cube(b)
|
||||
|
||||
cr = q2c[qr]
|
||||
cg = q2c[qg]
|
||||
cb = q2c[qb]
|
||||
|
||||
# If we have hit the colour exactly, return early.
|
||||
if (cr == r and cg == g and cb == b):
|
||||
return 16 + (36 * qr) + (6 * qg) + qb
|
||||
|
||||
# Work out the closest grey (average of RGB).
|
||||
grey_avg = (r + g + b) // 3
|
||||
|
||||
if grey_avg > 238:
|
||||
grey_idx = 23
|
||||
else:
|
||||
grey_idx = (grey_avg - 3) // 10
|
||||
|
||||
grey = 8 + (10 * grey_idx)
|
||||
|
||||
# Is grey or 6x6x6 colour closest?
|
||||
d = colour_dist_sq(cr, cg, cb, r, g, b)
|
||||
|
||||
if colour_dist_sq(grey, grey, grey, r, g, b) < d:
|
||||
idx = 232 + grey_idx
|
||||
else:
|
||||
idx = 16 + (36 * qr) + (6 * qg) + qb
|
||||
|
||||
return idx
|
||||
|
||||
|
||||
def color_html_to_weechat(color):
|
||||
# type: (str) -> str
|
||||
first_16 = {
|
||||
(0, 0, 0): "black", # 0
|
||||
(128, 0, 0): "red", # 1
|
||||
(0, 128, 0): "green", # 2
|
||||
(128, 128, 0): "brown", # 3
|
||||
(0, 0, 128): "blue", # 4
|
||||
(128, 0, 128): "magenta", # 5
|
||||
(0, 128, 128): "cyan", # 6
|
||||
(192, 192, 192): "default", # 7
|
||||
(128, 128, 128): "gray", # 8
|
||||
(255, 0, 0): "lightred", # 9
|
||||
(0, 255, 0): "lightgreen", # 11
|
||||
(255, 255, 0): "yellow", # 12
|
||||
(0, 0, 255): "lightblue", # 13
|
||||
(255, 0, 255): "lightmagenta", # 14
|
||||
(0, 255, 255): "lightcyan", # 15
|
||||
}
|
||||
|
||||
try:
|
||||
rgb_color = webcolors.html5_parse_legacy_color(color)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if rgb_color in first_16:
|
||||
return first_16[rgb_color]
|
||||
|
||||
return str(colour_find_rgb(*rgb_color))
|
||||
|
||||
|
||||
def color_weechat_to_html(color):
|
||||
first_16 = {
|
||||
"black": "black", # 0
|
||||
"red": "maroon", # 1
|
||||
"green": "green", # 2
|
||||
"brown": "olive", # 3
|
||||
"blue": "navy", # 4
|
||||
"magenta": "purple", # 5
|
||||
"cyan": "teal", # 6
|
||||
"default": "silver", # 7
|
||||
"gray": "grey", # 8
|
||||
"lightred": "red", # 9
|
||||
"lightgreen": "lime", # 11
|
||||
"yellow": "yellow", # 12
|
||||
"lightblue": "fuchsia", # 13
|
||||
"lightmagenta": "aqua", # 14
|
||||
"lightcyan": "white", # 15
|
||||
}
|
||||
|
||||
hex_colors = {
|
||||
"0": "#000000",
|
||||
"1": "#800000",
|
||||
"2": "#008000",
|
||||
"3": "#808000",
|
||||
"4": "#000080",
|
||||
"5": "#800080",
|
||||
"6": "#008080",
|
||||
"7": "#c0c0c0",
|
||||
"8": "#808080",
|
||||
"9": "#ff0000",
|
||||
"10": "#00ff00",
|
||||
"11": "#ffff00",
|
||||
"12": "#0000ff",
|
||||
"13": "#ff00ff",
|
||||
"14": "#00ffff",
|
||||
"15": "#ffffff",
|
||||
"16": "#000000",
|
||||
"17": "#00005f",
|
||||
"18": "#000087",
|
||||
"19": "#0000af",
|
||||
"20": "#0000d7",
|
||||
"21": "#0000ff",
|
||||
"22": "#005f00",
|
||||
"23": "#005f5f",
|
||||
"24": "#005f87",
|
||||
"25": "#005faf",
|
||||
"26": "#005fd7",
|
||||
"27": "#005fff",
|
||||
"28": "#008700",
|
||||
"29": "#00875f",
|
||||
"30": "#008787",
|
||||
"31": "#0087af",
|
||||
"32": "#0087d7",
|
||||
"33": "#0087ff",
|
||||
"34": "#00af00",
|
||||
"35": "#00af5f",
|
||||
"36": "#00af87",
|
||||
"37": "#00afaf",
|
||||
"38": "#00afd7",
|
||||
"39": "#00afff",
|
||||
"40": "#00d700",
|
||||
"41": "#00d75f",
|
||||
"42": "#00d787",
|
||||
"43": "#00d7af",
|
||||
"44": "#00d7d7",
|
||||
"45": "#00d7ff",
|
||||
"46": "#00ff00",
|
||||
"47": "#00ff5f",
|
||||
"48": "#00ff87",
|
||||
"49": "#00ffaf",
|
||||
"50": "#00ffd7",
|
||||
"51": "#00ffff",
|
||||
"52": "#5f0000",
|
||||
"53": "#5f005f",
|
||||
"54": "#5f0087",
|
||||
"55": "#5f00af",
|
||||
"56": "#5f00d7",
|
||||
"57": "#5f00ff",
|
||||
"58": "#5f5f00",
|
||||
"59": "#5f5f5f",
|
||||
"60": "#5f5f87",
|
||||
"61": "#5f5faf",
|
||||
"62": "#5f5fd7",
|
||||
"63": "#5f5fff",
|
||||
"64": "#5f8700",
|
||||
"65": "#5f875f",
|
||||
"66": "#5f8787",
|
||||
"67": "#5f87af",
|
||||
"68": "#5f87d7",
|
||||
"69": "#5f87ff",
|
||||
"70": "#5faf00",
|
||||
"71": "#5faf5f",
|
||||
"72": "#5faf87",
|
||||
"73": "#5fafaf",
|
||||
"74": "#5fafd7",
|
||||
"75": "#5fafff",
|
||||
"76": "#5fd700",
|
||||
"77": "#5fd75f",
|
||||
"78": "#5fd787",
|
||||
"79": "#5fd7af",
|
||||
"80": "#5fd7d7",
|
||||
"81": "#5fd7ff",
|
||||
"82": "#5fff00",
|
||||
"83": "#5fff5f",
|
||||
"84": "#5fff87",
|
||||
"85": "#5fffaf",
|
||||
"86": "#5fffd7",
|
||||
"87": "#5fffff",
|
||||
"88": "#870000",
|
||||
"89": "#87005f",
|
||||
"90": "#870087",
|
||||
"91": "#8700af",
|
||||
"92": "#8700d7",
|
||||
"93": "#8700ff",
|
||||
"94": "#875f00",
|
||||
"95": "#875f5f",
|
||||
"96": "#875f87",
|
||||
"97": "#875faf",
|
||||
"98": "#875fd7",
|
||||
"99": "#875fff",
|
||||
"100": "#878700",
|
||||
"101": "#87875f",
|
||||
"102": "#878787",
|
||||
"103": "#8787af",
|
||||
"104": "#8787d7",
|
||||
"105": "#8787ff",
|
||||
"106": "#87af00",
|
||||
"107": "#87af5f",
|
||||
"108": "#87af87",
|
||||
"109": "#87afaf",
|
||||
"110": "#87afd7",
|
||||
"111": "#87afff",
|
||||
"112": "#87d700",
|
||||
"113": "#87d75f",
|
||||
"114": "#87d787",
|
||||
"115": "#87d7af",
|
||||
"116": "#87d7d7",
|
||||
"117": "#87d7ff",
|
||||
"118": "#87ff00",
|
||||
"119": "#87ff5f",
|
||||
"120": "#87ff87",
|
||||
"121": "#87ffaf",
|
||||
"122": "#87ffd7",
|
||||
"123": "#87ffff",
|
||||
"124": "#af0000",
|
||||
"125": "#af005f",
|
||||
"126": "#af0087",
|
||||
"127": "#af00af",
|
||||
"128": "#af00d7",
|
||||
"129": "#af00ff",
|
||||
"130": "#af5f00",
|
||||
"131": "#af5f5f",
|
||||
"132": "#af5f87",
|
||||
"133": "#af5faf",
|
||||
"134": "#af5fd7",
|
||||
"135": "#af5fff",
|
||||
"136": "#af8700",
|
||||
"137": "#af875f",
|
||||
"138": "#af8787",
|
||||
"139": "#af87af",
|
||||
"140": "#af87d7",
|
||||
"141": "#af87ff",
|
||||
"142": "#afaf00",
|
||||
"143": "#afaf5f",
|
||||
"144": "#afaf87",
|
||||
"145": "#afafaf",
|
||||
"146": "#afafd7",
|
||||
"147": "#afafff",
|
||||
"148": "#afd700",
|
||||
"149": "#afd75f",
|
||||
"150": "#afd787",
|
||||
"151": "#afd7af",
|
||||
"152": "#afd7d7",
|
||||
"153": "#afd7ff",
|
||||
"154": "#afff00",
|
||||
"155": "#afff5f",
|
||||
"156": "#afff87",
|
||||
"157": "#afffaf",
|
||||
"158": "#afffd7",
|
||||
"159": "#afffff",
|
||||
"160": "#d70000",
|
||||
"161": "#d7005f",
|
||||
"162": "#d70087",
|
||||
"163": "#d700af",
|
||||
"164": "#d700d7",
|
||||
"165": "#d700ff",
|
||||
"166": "#d75f00",
|
||||
"167": "#d75f5f",
|
||||
"168": "#d75f87",
|
||||
"169": "#d75faf",
|
||||
"170": "#d75fd7",
|
||||
"171": "#d75fff",
|
||||
"172": "#d78700",
|
||||
"173": "#d7875f",
|
||||
"174": "#d78787",
|
||||
"175": "#d787af",
|
||||
"176": "#d787d7",
|
||||
"177": "#d787ff",
|
||||
"178": "#d7af00",
|
||||
"179": "#d7af5f",
|
||||
"180": "#d7af87",
|
||||
"181": "#d7afaf",
|
||||
"182": "#d7afd7",
|
||||
"183": "#d7afff",
|
||||
"184": "#d7d700",
|
||||
"185": "#d7d75f",
|
||||
"186": "#d7d787",
|
||||
"187": "#d7d7af",
|
||||
"188": "#d7d7d7",
|
||||
"189": "#d7d7ff",
|
||||
"190": "#d7ff00",
|
||||
"191": "#d7ff5f",
|
||||
"192": "#d7ff87",
|
||||
"193": "#d7ffaf",
|
||||
"194": "#d7ffd7",
|
||||
"195": "#d7ffff",
|
||||
"196": "#ff0000",
|
||||
"197": "#ff005f",
|
||||
"198": "#ff0087",
|
||||
"199": "#ff00af",
|
||||
"200": "#ff00d7",
|
||||
"201": "#ff00ff",
|
||||
"202": "#ff5f00",
|
||||
"203": "#ff5f5f",
|
||||
"204": "#ff5f87",
|
||||
"205": "#ff5faf",
|
||||
"206": "#ff5fd7",
|
||||
"207": "#ff5fff",
|
||||
"208": "#ff8700",
|
||||
"209": "#ff875f",
|
||||
"210": "#ff8787",
|
||||
"211": "#ff87af",
|
||||
"212": "#ff87d7",
|
||||
"213": "#ff87ff",
|
||||
"214": "#ffaf00",
|
||||
"215": "#ffaf5f",
|
||||
"216": "#ffaf87",
|
||||
"217": "#ffafaf",
|
||||
"218": "#ffafd7",
|
||||
"219": "#ffafff",
|
||||
"220": "#ffd700",
|
||||
"221": "#ffd75f",
|
||||
"222": "#ffd787",
|
||||
"223": "#ffd7af",
|
||||
"224": "#ffd7d7",
|
||||
"225": "#ffd7ff",
|
||||
"226": "#ffff00",
|
||||
"227": "#ffff5f",
|
||||
"228": "#ffff87",
|
||||
"229": "#ffffaf",
|
||||
"230": "#ffffd7",
|
||||
"231": "#ffffff",
|
||||
"232": "#080808",
|
||||
"233": "#121212",
|
||||
"234": "#1c1c1c",
|
||||
"235": "#262626",
|
||||
"236": "#303030",
|
||||
"237": "#3a3a3a",
|
||||
"238": "#444444",
|
||||
"239": "#4e4e4e",
|
||||
"240": "#585858",
|
||||
"241": "#626262",
|
||||
"242": "#6c6c6c",
|
||||
"243": "#767676",
|
||||
"244": "#808080",
|
||||
"245": "#8a8a8a",
|
||||
"246": "#949494",
|
||||
"247": "#9e9e9e",
|
||||
"248": "#a8a8a8",
|
||||
"249": "#b2b2b2",
|
||||
"250": "#bcbcbc",
|
||||
"251": "#c6c6c6",
|
||||
"252": "#d0d0d0",
|
||||
"253": "#dadada",
|
||||
"254": "#e4e4e4",
|
||||
"255": "#eeeeee"
|
||||
}
|
||||
|
||||
if color in first_16:
|
||||
return first_16[color]
|
||||
|
||||
hex_color = hex_colors[color]
|
||||
|
||||
try:
|
||||
return webcolors.hex_to_name(hex_color)
|
||||
except ValueError:
|
||||
return hex_color
|
||||
|
||||
|
||||
# TODO reverse video
|
||||
def parse_input_line(line):
|
||||
"""Parses the weechat input line and produces formatted strings that can be
|
||||
later converted to HTML or to a string for weechat's print functions
|
||||
"""
|
||||
# type: (str) -> List[FormattedString]
|
||||
text = "" # type: str
|
||||
substrings = [] # type: List[FormattedString]
|
||||
attributes = DEFAULT_ATRIBUTES.copy()
|
||||
|
||||
i = 0
|
||||
while i < len(line):
|
||||
# Bold
|
||||
if line[i] == "\x02":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["bold"] = not attributes["bold"]
|
||||
i = i + 1
|
||||
|
||||
# Color
|
||||
elif line[i] == "\x03":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
i = i + 1
|
||||
|
||||
# check if it's a valid color, add it to the attributes
|
||||
if line[i].isdigit():
|
||||
color_string = line[i]
|
||||
i = i + 1
|
||||
|
||||
if line[i].isdigit():
|
||||
if color_string == "0":
|
||||
color_string = line[i]
|
||||
else:
|
||||
color_string = color_string + line[i]
|
||||
i = i + 1
|
||||
|
||||
attributes["fgcolor"] = color_line_to_weechat(color_string)
|
||||
else:
|
||||
attributes["fgcolor"] = None
|
||||
|
||||
# check if we have a background color
|
||||
if line[i] == "," and line[i+1].isdigit():
|
||||
color_string = line[i+1]
|
||||
i = i + 2
|
||||
|
||||
if line[i].isdigit():
|
||||
if color_string == "0":
|
||||
color_string = line[i]
|
||||
else:
|
||||
color_string = color_string + line[i]
|
||||
i = i + 1
|
||||
|
||||
attributes["bgcolor"] = color_line_to_weechat(color_string)
|
||||
else:
|
||||
attributes["bgcolor"] = None
|
||||
# Reset
|
||||
elif line[i] == "\x0F":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
# Reset all the attributes
|
||||
attributes = DEFAULT_ATRIBUTES.copy()
|
||||
i = i + 1
|
||||
# Italic
|
||||
elif line[i] == "\x1D":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["italic"] = not attributes["italic"]
|
||||
i = i + 1
|
||||
|
||||
# Underline
|
||||
elif line[i] == "\x1F":
|
||||
if text:
|
||||
substrings.append(FormattedString(text, attributes.copy()))
|
||||
text = ""
|
||||
attributes["underline"] = not attributes["underline"]
|
||||
i = i + 1
|
||||
|
||||
# Normal text
|
||||
else:
|
||||
text = text + line[i]
|
||||
i = i + 1
|
||||
|
||||
substrings.append(FormattedString(text, attributes))
|
||||
return substrings
|
||||
|
||||
|
||||
def formatted(strings):
|
||||
for string in strings:
|
||||
if string.attributes != DEFAULT_ATRIBUTES:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def formatted_to_weechat(strings):
|
||||
# type: (List[FormattedString]) -> str
|
||||
# TODO BG COLOR
|
||||
def add_attribute(string, name, value):
|
||||
if name == "bold" and value:
|
||||
return "{bold_on}{text}{bold_off}".format(
|
||||
bold_on=W.color("bold"),
|
||||
text=string,
|
||||
bold_off=W.color("-bold"))
|
||||
|
||||
elif name == "italic" and value:
|
||||
return "{italic_on}{text}{italic_off}".format(
|
||||
italic_on=W.color("italic"),
|
||||
text=string,
|
||||
italic_off=W.color("-italic"))
|
||||
|
||||
elif name == "underline" and value:
|
||||
return "{underline_on}{text}{underline_off}".format(
|
||||
underline_on=W.color("underline"),
|
||||
text=string,
|
||||
underline_off=W.color("-underline"))
|
||||
|
||||
elif name == "strikethrough" and value:
|
||||
return string_strikethrough(string)
|
||||
|
||||
elif name == "quote" and value:
|
||||
return "“{text}”".format(text=string)
|
||||
|
||||
elif name == "fgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on=W.color(value),
|
||||
text=string,
|
||||
color_off=W.color("resetcolor"))
|
||||
|
||||
elif name == "bgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on=W.color("," + value),
|
||||
text=string,
|
||||
color_off=W.color("resetcolor"))
|
||||
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = add_attribute(text, key, value)
|
||||
return text
|
||||
|
||||
weechat_strings = map(format_string, strings)
|
||||
return "".join(weechat_strings)
|
||||
|
||||
|
||||
def formatted_to_html(strings):
|
||||
# type: (List[FormattedString]) -> str
|
||||
# TODO BG COLOR
|
||||
def add_attribute(string, name, value):
|
||||
if name == "bold" and value:
|
||||
return "{bold_on}{text}{bold_off}".format(
|
||||
bold_on="<strong>",
|
||||
text=string,
|
||||
bold_off="</strong>")
|
||||
elif name == "italic" and value:
|
||||
return "{italic_on}{text}{italic_off}".format(
|
||||
italic_on="<em>",
|
||||
text=string,
|
||||
italic_off="</em>")
|
||||
elif name == "underline" and value:
|
||||
return "{underline_on}{text}{underline_off}".format(
|
||||
underline_on="<u>",
|
||||
text=string,
|
||||
underline_off="</u>")
|
||||
elif name == "strikethrough" and value:
|
||||
return "{strike_on}{text}{strike_off}".format(
|
||||
strike_on="<del>",
|
||||
text=string,
|
||||
strike_off="</del>")
|
||||
elif name == "quote" and value:
|
||||
return "{quote_on}{text}{quote_off}".format(
|
||||
quote_on="<blockquote>",
|
||||
text=string,
|
||||
quote_off="</blockquote>")
|
||||
elif name == "fgcolor" and value:
|
||||
return "{color_on}{text}{color_off}".format(
|
||||
color_on="<font color={color}>".format(
|
||||
color=color_weechat_to_html(value)
|
||||
),
|
||||
text=string,
|
||||
color_off="</font>")
|
||||
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = add_attribute(text, key, value)
|
||||
return text
|
||||
|
||||
html_string = map(format_string, strings)
|
||||
return "".join(html_string)
|
||||
|
||||
|
||||
# TODO do we want at least some formating using unicode
|
||||
# (strikethrough, quotes)?
|
||||
def formatted_to_plain(strings):
|
||||
# type: (List[FormattedString]) -> str
|
||||
def strip_atribute(string, _, __):
|
||||
return string
|
||||
|
||||
def format_string(formatted_string):
|
||||
text = formatted_string.text
|
||||
attributes = formatted_string.attributes
|
||||
|
||||
for key, value in attributes.items():
|
||||
text = strip_atribute(text, key, value)
|
||||
return text
|
||||
|
||||
plain_string = map(format_string, strings)
|
||||
return "".join(plain_string)
|
||||
|
||||
|
||||
class MatrixHtmlParser(HTMLParser):
|
||||
# TODO bg color
|
||||
# TODO bullets
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self)
|
||||
self.text = "" # type: str
|
||||
self.substrings = [] # type: List[FormattedString]
|
||||
self.attributes = DEFAULT_ATRIBUTES.copy()
|
||||
|
||||
def _toggle_attribute(self, attribute):
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes[attribute] = not self.attributes[attribute]
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "strong":
|
||||
self._toggle_attribute("bold")
|
||||
elif tag == "em":
|
||||
self._toggle_attribute("italic")
|
||||
elif tag == "u":
|
||||
self._toggle_attribute("underline")
|
||||
elif tag == "del":
|
||||
self._toggle_attribute("strikethrough")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "font":
|
||||
for key, value in attrs:
|
||||
if key == "color":
|
||||
color = color_html_to_weechat(value)
|
||||
|
||||
if not color:
|
||||
continue
|
||||
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes["fgcolor"] = color
|
||||
else:
|
||||
# W.prnt("", "Unhandled tag {t}".format(t=tag))
|
||||
pass
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "strong":
|
||||
self._toggle_attribute("bold")
|
||||
elif tag == "em":
|
||||
self._toggle_attribute("italic")
|
||||
elif tag == "u":
|
||||
self._toggle_attribute("underline")
|
||||
elif tag == "del":
|
||||
self._toggle_attribute("strikethrough")
|
||||
elif tag == "blockquote":
|
||||
self._toggle_attribute("quote")
|
||||
elif tag == "font":
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
self.text = ""
|
||||
self.attributes["fgcolor"] = None
|
||||
else:
|
||||
pass
|
||||
|
||||
def handle_data(self, data):
|
||||
self.text = self.text + data
|
||||
|
||||
def get_substrings(self):
|
||||
if self.text:
|
||||
self.substrings.append(
|
||||
FormattedString(self.text, self.attributes.copy())
|
||||
)
|
||||
|
||||
return self.substrings
|
||||
|
||||
|
||||
def html_to_formatted(html):
|
||||
parser = MatrixHtmlParser()
|
||||
parser.feed(html)
|
||||
return parser.get_substrings()
|
||||
|
||||
|
||||
def wrap_socket(server, file_descriptor):
|
||||
# type: (MatrixServer, int) -> socket.socket
|
||||
sock = None # type: socket.socket
|
||||
|
@ -1501,10 +669,10 @@ def handle_http_response(server, message):
|
|||
reason = ("." if not response or not response["error"] else
|
||||
": {r}.".format(r=response["error"]))
|
||||
|
||||
message = ("{prefix}Login error{reason}").format(
|
||||
error_message = ("{prefix}Login error{reason}").format(
|
||||
prefix=W.prefix("error"),
|
||||
reason=reason)
|
||||
server_buffer_prnt(server, message)
|
||||
server_buffer_prnt(server, error_message)
|
||||
|
||||
W.unhook(server.timer_hook)
|
||||
server.timer_hook = None
|
||||
|
@ -1516,16 +684,16 @@ def handle_http_response(server, message):
|
|||
reason = ("." if not response or not response["error"] else
|
||||
": {r}.".format(r=response["error"]))
|
||||
|
||||
message = ("{prefix}Can't set state{reason}").format(
|
||||
error_message = ("{prefix}Can't set state{reason}").format(
|
||||
prefix=W.prefix("network"),
|
||||
reason=reason)
|
||||
server_buffer_prnt(server, message)
|
||||
server_buffer_prnt(server, error_message)
|
||||
else:
|
||||
message = ("{prefix}Unhandled 403 error, please inform the "
|
||||
error_message = ("{prefix}Unhandled 403 error, please inform the "
|
||||
"developers about this: {error}").format(
|
||||
prefix=W.prefix("error"),
|
||||
error=message.response.body)
|
||||
server_buffer_prnt(server, message)
|
||||
server_buffer_prnt(server, error_message)
|
||||
|
||||
else:
|
||||
server_buffer_prnt(
|
||||
|
@ -1541,7 +709,7 @@ def handle_http_response(server, message):
|
|||
|
||||
creation_date = datetime.datetime.fromtimestamp(message.creation_time)
|
||||
done_time = time.time()
|
||||
message = ("Message of type {t} created at {c}."
|
||||
info_message = ("Message of type {t} created at {c}."
|
||||
"\nMessage lifetime information:"
|
||||
"\n Send delay: {s} ms"
|
||||
"\n Receive delay: {r} ms"
|
||||
|
@ -1553,7 +721,7 @@ def handle_http_response(server, message):
|
|||
r=(message.receive_time - message.send_time) * 1000,
|
||||
h=(done_time - message.receive_time) * 1000,
|
||||
total=(done_time - message.creation_time) * 1000,)
|
||||
prnt_debug(DebugType.TIMING, server, message)
|
||||
prnt_debug(DebugType.TIMING, server, info_message)
|
||||
|
||||
return
|
||||
|
||||
|
@ -1709,9 +877,9 @@ def matrix_handle_room_text_message(server, room_id, event, old=False):
|
|||
|
||||
if 'format' in event['content'] and 'formatted_body' in event['content']:
|
||||
if event['content']['format'] == "org.matrix.custom.html":
|
||||
formatted_data = html_to_formatted(
|
||||
formatted_data = colors.html_to_formatted(
|
||||
event['content']['formatted_body'])
|
||||
msg = formatted_to_weechat(formatted_data)
|
||||
msg = colors.formatted_to_weechat(W, formatted_data)
|
||||
|
||||
if event['sender'] in room.users:
|
||||
user = room.users[event['sender']]
|
||||
|
@ -2626,17 +1794,20 @@ def room_input_cb(server_name, buffer, input_data):
|
|||
if room.encrypted:
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
formatted_data = parse_input_line(input_data)
|
||||
formatted_data = colors.parse_input_line(input_data)
|
||||
|
||||
body = {"msgtype": "m.text", "body": formatted_to_plain(formatted_data)}
|
||||
body = {
|
||||
"msgtype": "m.text",
|
||||
"body": colors.formatted_to_plain(formatted_data)
|
||||
}
|
||||
|
||||
if formatted(formatted_data):
|
||||
if colors.formatted(formatted_data):
|
||||
body["format"] = "org.matrix.custom.html"
|
||||
body["formatted_body"] = formatted_to_html(formatted_data)
|
||||
body["formatted_body"] = colors.formatted_to_html(formatted_data)
|
||||
|
||||
extra_data = {
|
||||
"author": server.user,
|
||||
"message": formatted_to_weechat(formatted_data),
|
||||
"message": colors.formatted_to_weechat(W, formatted_data),
|
||||
"room_id": room_id
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue