-
Notifications
You must be signed in to change notification settings - Fork 0
/
zephyr_to_html.py
162 lines (142 loc) · 5.86 KB
/
zephyr_to_html.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""
Code converted from original Pidgin C code.
https://github.com/Intika-Pidgin/Pidgin/blob/master/libpurple/protocols/zephyr/zephyr.c#L614 (mirror that still has Zephyr)
"""
class _ZFrame:
# true for everything but @color, since inside the parens of that one is the color
has_closer: bool
# }, ], ), >
closer: str
# </i>, </font>, </b>, etc.
closing: str
# text including the opening html thingie.
text: str
# href for links
# NOTE: this is unused, copied from original code
is_href: bool
href: str
enclosing: object # _ZFrame but I can't use itself as a type
def zephyr_to_html(message):
"""
Convert Zephyr to idiomatic Matrix HTML per https://spec.matrix.org/v1.13/client-server-api/#mroommessage-msgtypes
>>> zephyr_to_html("hello@@world.com")
>>> zephyr_to_html("@{@color(blue)@i(hello)}")
'<span data-mx-color="blue"><i>hello</i></span>'
>>> zephyr_to_html("@b[hello @i{world @color(blue)this is a test}]")
'<b>hello <i>world <span data-mx-color="blue">this is a test</span></i></b>'
>>> zephyr_to_html("[email protected], someone forgot to escape the @")
'[email protected], someone forgot to escape the @'
"""
frames: _ZFrame
frames = _ZFrame()
frames.text = ""
frames.enclosing = None
frames.closing = ""
frames.has_closer = False
frames.closer = None
while message:
if message[0] == '@' and len(message) > 1 and message[1] == '@':
frames.text += "@"
message = message[2:]
elif message[0] == '@':
end = 1
while end < len(message) and (message[end].isalnum() or message[end] == '_'):
end += 1
if end < len(message) and (message[end] in "{[(" or message[end:].startswith("<")):
buf: str = message[1:end]
message = message[end:]
new_f = _ZFrame()
new_f.enclosing = frames
new_f.has_closer = True
new_f.closer = (
")" if message[0] == '(' else
"]" if message[0] == '[' else
"}" if message[0] == '{' else
">"
)
message = message[(4 if message[0] == '&' else 1):]
if buf.lower() in ("italic", "i"):
new_f.text = "<i>"
new_f.closing = "</i>"
elif buf.lower() == "huge":
new_f.text = "<h1>"
new_f.closing = "</h1>"
elif buf.lower() == "large":
new_f.text = "<h2>"
new_f.closing = "</h2>"
elif buf.lower() == "medium":
# Presumably this is normal size, leaving the same for now
# TODO: do something better
new_f.text = "<font size=\"3\">"
new_f.closing = "</font>"
elif buf.lower() == "small":
# subscript is probably the closest thing
new_f.text = "<sub>"
new_f.closing = "</sub>"
elif buf.lower() in ("bold", "b"):
new_f.text = "<b>"
new_f.closing = "</b>"
elif buf.lower() == "font":
# Changing the font is not currently supported by Matrix, leaving the same for now
# https://spec.matrix.org/v1.13/client-server-api/#mroommessage-msgtypes
extra_f = _ZFrame()
extra_f.enclosing = frames
new_f.enclosing = extra_f
extra_f.text = ""
extra_f.has_closer = False
extra_f.closer = frames.closer
extra_f.closing = "</font>"
new_f.text = "<font face=\""
new_f.closing = "\">"
elif buf.lower() == "color":
extra_f = _ZFrame()
extra_f.enclosing = frames
new_f.enclosing = extra_f
extra_f.text = ""
extra_f.has_closer = False
extra_f.closer = frames.closer
extra_f.closing = "</span>"
new_f.text = "<span data-mx-color=\""
new_f.closing = "\">"
else:
new_f.text = ""
new_f.closing = ""
frames = new_f
else:
# Not a formatting tag, add the character as normal.
frames.text += message[0]
message = message[1:]
elif frames.closer and message.startswith(frames.closer):
popped: _ZFrame
last_had_closer: bool = False
message = message[len(frames.closer):]
if frames.enclosing:
while True:
popped = frames
frames = frames.enclosing
frames.text += popped.text
frames.text += popped.closing
popped.text = ""
last_had_closer = popped.has_closer
# simulate do while loop from C code
if frames.enclosing is None or last_had_closer:
break
else:
frames.text += message
elif message[0] == '\n':
frames.text += "<br>"
message = message[1:]
else:
frames.text += message[0]
message = message[1:]
# go through all the stuff that they didn't close
while frames.enclosing is not None:
frames.enclosing.text += frames.text
frames.enclosing.text += frames.closing
frames.text = ""
frames = frames.enclosing
return frames.text
if __name__ == "__main__":
import doctest
doctest.testmod()