Start splitting up the script into different files.

This commit is contained in:
poljar (Damir Jelić) 2018-01-26 12:18:52 +01:00
parent 704cadc5c0
commit 62adae5aa3
3 changed files with 885 additions and 869 deletions

0
matrix/__init__.py Normal file
View file

845
matrix/colors.py Normal file
View 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)

View file

@ -22,13 +22,8 @@ from functools import wraps
# pylint: disable=unused-import # pylint: disable=unused-import
from typing import (List, Set, Dict, Tuple, Text, Optional, AnyStr, Deque, Any) from typing import (List, Set, Dict, Tuple, Text, Optional, AnyStr, Deque, Any)
import webcolors
from http_parser.pyparser import HttpParser from http_parser.pyparser import HttpParser
from matrix import colors
try:
from HTMLParser import HTMLParser
except ImportError:
from html.parser import HTMLParser
# pylint: disable=import-error # pylint: disable=import-error
@ -275,14 +270,14 @@ class MatrixMessage:
): ):
# type: (...) -> None # type: (...) -> None
# pylint: disable=dangerous-default-value # pylint: disable=dangerous-default-value
self.type = message_type # MessageType self.type = message_type # type: MessageType
self.request = None # HttpRequest self.request = None # type: HttpRequest
self.response = None # HttpResponse self.response = None # type: HttpResponse
self.extra_data = extra_data # Dict[str, Any] self.extra_data = extra_data # type: Dict[str, Any]
self.creation_time = time.time() self.creation_time = time.time() # type: float
self.send_time = None self.send_time = None # type: float
self.receive_time = None self.receive_time = None # type: float
if message_type == MessageType.LOGIN: if message_type == MessageType.LOGIN:
path = ("{api}/login").format(api=MATRIX_API_PATH) path = ("{api}/login").format(api=MATRIX_API_PATH)
@ -596,833 +591,6 @@ class MatrixServer:
"", "server_config_change_cb", self.name, "", "") "", "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): def wrap_socket(server, file_descriptor):
# type: (MatrixServer, int) -> socket.socket # type: (MatrixServer, int) -> socket.socket
sock = None # type: 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 reason = ("." if not response or not response["error"] else
": {r}.".format(r=response["error"])) ": {r}.".format(r=response["error"]))
message = ("{prefix}Login error{reason}").format( error_message = ("{prefix}Login error{reason}").format(
prefix=W.prefix("error"), prefix=W.prefix("error"),
reason=reason) reason=reason)
server_buffer_prnt(server, message) server_buffer_prnt(server, error_message)
W.unhook(server.timer_hook) W.unhook(server.timer_hook)
server.timer_hook = None server.timer_hook = None
@ -1516,16 +684,16 @@ def handle_http_response(server, message):
reason = ("." if not response or not response["error"] else reason = ("." if not response or not response["error"] else
": {r}.".format(r=response["error"])) ": {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"), prefix=W.prefix("network"),
reason=reason) reason=reason)
server_buffer_prnt(server, message) server_buffer_prnt(server, error_message)
else: else:
message = ("{prefix}Unhandled 403 error, please inform the " error_message = ("{prefix}Unhandled 403 error, please inform the "
"developers about this: {error}").format( "developers about this: {error}").format(
prefix=W.prefix("error"), prefix=W.prefix("error"),
error=message.response.body) error=message.response.body)
server_buffer_prnt(server, message) server_buffer_prnt(server, error_message)
else: else:
server_buffer_prnt( server_buffer_prnt(
@ -1541,7 +709,7 @@ def handle_http_response(server, message):
creation_date = datetime.datetime.fromtimestamp(message.creation_time) creation_date = datetime.datetime.fromtimestamp(message.creation_time)
done_time = time.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:" "\nMessage lifetime information:"
"\n Send delay: {s} ms" "\n Send delay: {s} ms"
"\n Receive delay: {r} ms" "\n Receive delay: {r} ms"
@ -1553,7 +721,7 @@ def handle_http_response(server, message):
r=(message.receive_time - message.send_time) * 1000, r=(message.receive_time - message.send_time) * 1000,
h=(done_time - message.receive_time) * 1000, h=(done_time - message.receive_time) * 1000,
total=(done_time - message.creation_time) * 1000,) total=(done_time - message.creation_time) * 1000,)
prnt_debug(DebugType.TIMING, server, message) prnt_debug(DebugType.TIMING, server, info_message)
return 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 'format' in event['content'] and 'formatted_body' in event['content']:
if event['content']['format'] == "org.matrix.custom.html": if event['content']['format'] == "org.matrix.custom.html":
formatted_data = html_to_formatted( formatted_data = colors.html_to_formatted(
event['content']['formatted_body']) event['content']['formatted_body'])
msg = formatted_to_weechat(formatted_data) msg = colors.formatted_to_weechat(W, formatted_data)
if event['sender'] in room.users: if event['sender'] in room.users:
user = room.users[event['sender']] user = room.users[event['sender']]
@ -2626,17 +1794,20 @@ def room_input_cb(server_name, buffer, input_data):
if room.encrypted: if room.encrypted:
return W.WEECHAT_RC_OK 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["format"] = "org.matrix.custom.html"
body["formatted_body"] = formatted_to_html(formatted_data) body["formatted_body"] = colors.formatted_to_html(formatted_data)
extra_data = { extra_data = {
"author": server.user, "author": server.user,
"message": formatted_to_weechat(formatted_data), "message": colors.formatted_to_weechat(W, formatted_data),
"room_id": room_id "room_id": room_id
} }