-
Notifications
You must be signed in to change notification settings - Fork 0
/
zephyr_receive.py
155 lines (132 loc) · 5.65 KB
/
zephyr_receive.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
import zephyr
import sys
from constants import *
from zephyr_client import Zephyr
from util import strip_default_realm, create_zephyr_room, get_zephyr_localpart, timestamp_zephyr_to_matrix, is_reasonable_signature
from kerberos import renew_kerberos_tickets
from config import config
import matrix
from zephyr_to_html import zephyr_to_html
z: Zephyr = Zephyr()
def handle_bot_command(message: str, sender: str) -> str:
"""
Synchronously get the output of a command
"""
message = message.strip()
command = message.split(' ')[0]
if command == 'ping':
return 'pong'
elif command == 'echo':
body = message.split(' ', 1)[1:][0]
return body
elif command == 'add':
try:
cls, instance, recipient = message.split(' ')[1:]
line = ','.join([cls, instance, recipient])
if '\\' in line or '"' in line:
return 'Error: Backslashes/quotes are not currently supported'
z.subscribe(cls, instance)
return f"Successfully subscribed to -c {cls} -i {instance}"
except ValueError as e:
return f'Error: `add` expects exactly 3 parameters separated by spaces: {e}'
elif command == 'send':
try:
recipient, message = message.split(' ', 2)[1:]
# TODO: direct messages are just rooms.
# using a library would take care of doing this for us
return 'This feature is not implemented (ask rgabriel to implement it)'
except ValueError:
return f'What do you wish to send to {sender}?'
elif command == 'help':
return 'Available commands are ping, echo, add, and send (unimplemented)'
else:
return f'Error: Unknown command {command}'
def on_zephyr_message(message: zephyr.ZNotice):
"""
Handles Zephyr message received (to bridge to Matrix)
"""
print(message.__dict__)
sender = strip_default_realm(message.sender)
recipient = strip_default_realm(message.recipient)
# Pings are not messages
if message.opcode == 'PING':
# TODO: bridge if actually relevant
# (only seems to work for DMs, which would make it mostly pointless)
print(f"{sender} is typing...")
return
# Make sure message has 2 (usually) or 1 (rare) fields
if len(message.fields) == 1:
# In my testing, some messages without signature were sent as one field
signature = sender
content = message.fields[0]
elif len(message.fields) == 2:
signature, content = message.fields
else:
print(f"Message does not have 1 or 2 fields, skipping!", file=sys.stderr)
print(message.__dict__, file=sys.stderr)
return
# Respect blocked opcodes
if message.opcode in config.blocked_opcodes:
print(f"Not bridging blocked opcode: {message.opcode}")
return
# Ignore messages from Mattermost user
if sender in config.blocked_zephyr_usernames:
print(f"Not bridging blocked sender: {message.sender}")
return
# Treat empty signature as just kerb
if signature == '':
signature = sender
# This is a DM!
if message.cls.upper() == DEFAULT_CLASS and message.instance.upper() == DEFAULT_INSTANCE:
# If it's addressed to us, handle it as a command
if recipient == OWN_KERB:
print(f"Handling command from {sender}")
z.send_message(handle_bot_command(content, sender), recipient=sender)
else:
print(f"Skipped DM from {sender} ({signature}) to {recipient}:\n{content}", file=sys.stderr)
return
# TODO: handle lack of success ↓
room_alias = f'#{get_zephyr_localpart(message.cls, message.instance)}:{config.homeserver}'
# Create room (if needed)
if not matrix.get_room_id(room_alias):
create_zephyr_room(message.cls, message.instance)
# Create user (if needed)
matrix.create_user(f'{config.zephyr_user_prefix}{sender}')
matrix_user_id = f'@{config.zephyr_user_prefix}{sender}:{config.homeserver}'
# Get display name (either kerb or signature if it looks close enough to Hesiod's name)
display_name = signature if is_reasonable_signature(sender, signature) else sender
# Apply desired display name format
display_name = config.displayname_format.format(name=display_name)
# Adjust display name (if needed)
current_matrix_name = matrix.get_global_display_name(matrix_user_id)
if display_name and current_matrix_name != display_name:
matrix.set_global_display_name(matrix_user_id, display_name)
# Join user to room (if needed)
# TODO: check if in room, then join only if not
# mautrix Python has ensure_joined. it also has a store which can be simply json
matrix.join_room(room_alias, matrix_user_id)
message_plain = content.rstrip('\n') # remove trailing new lines
# Send message!
matrix.send_text_message(
room_id=room_alias,
message_plain=message_plain,
message_html=zephyr_to_html(message_plain),
user_id=f'@{config.zephyr_user_prefix}{sender}:{config.homeserver}',
additional_metadata={
'im.zephyr.authentic': message.auth,
'im.zephyr.signature': signature,
},
timestamp=timestamp_zephyr_to_matrix(message.time),
)
print(f"Sent [-c {msg.cls} -i {msg.instance}] {content}")
if __name__ == '__main__':
import os
while True:
# Catch zephyr exceptions at the top level
try:
msg: zephyr.ZNotice = zephyr.receive(True)
if msg is not None:
on_zephyr_message(msg)
except OSError as e:
renew_kerberos_tickets()
print(f"ZEPHYR ERROR: {e}")