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
|
import datetime
from functools import partial
from typing import NamedTuple
from quarg.quassel.types import MessageType
class User(NamedTuple):
nick: str
host: str
prefix: str
@classmethod
def from_sender(cls, sender, prefixes=''):
nick, *host = sender.split('!', 1)
prefix = prefixes[0] if prefixes else ''
return cls(nick, host[0] if host else '', prefix)
def __repr__(self):
return f'{self.prefix}{self.nick}'
class Message(NamedTuple):
type: MessageType
time: datetime.datetime
buffer: str
user: User
message: str
@classmethod
def from_backlog(cls, backlog):
return cls(MessageType(backlog.type), backlog.time, backlog.buffer.buffername,
User.from_sender(backlog.sender.sender, backlog.senderprefixes), backlog.message)
def format_from(backlog_row):
message = Message.from_backlog(backlog_row)
formatter = FORMATTERS[message.type]
timestamp = message.time.isoformat(sep=' ', timespec='seconds')
return f'{timestamp}\t{message.buffer}\t{formatter(message)}'
def format_part(msg):
if msg.message:
return f'<-- {msg.user} has left {msg.buffer} ({msg.message})'
return f'<-- {msg.user} has left {msg.buffer}'
def format_quit(msg):
if msg.message:
return f'<-- {msg.user} has quit ({msg.message})'
return f'<-- {msg.user} has quit'
def format_kick(msg):
target, *kickmsg = msg.message.split(' ', 1)
if kickmsg:
return f'<-* {msg.user} has kicked {target} from {msg.buffer} ({kickmsg[0]})'
return f'<-* {msg.user} has kicked {target} from {msg.buffer}'
def format_kill(msg):
# pylint: disable=line-too-long
# As of 2021-04-24 not even Quassel implements printing this message [1].
# They do have a symbol [2] for it, however, so use that along with the message
# [1] https://github.com/quassel/quassel/blob/285215315e6f2420724532323a4b1bccae156cb1/src/uisupport/uistyle.cpp#L950
# [2] https://github.com/quassel/quassel/blob/285215315e6f2420724532323a4b1bccae156cb1/src/uisupport/uistyle.cpp#L1079-L1080
return f'<-x {msg.message}'
def format_generic(msg):
return f'* {msg.message}'
def parse_netsplit(splitmsg):
# splitmsg contains user!host separated by #:# ...
elements = splitmsg.split('#:#')
# ... however, the last element contains the split servers instead
servers = elements.pop().split(' ', 1)
# This list can be unwieldily large. Mirror quassel's behaviour and cut off
# after printing 15 users. If there were any more users than this, have
# rest contain a value larger than 0
users = [User.from_sender(e) for e in elements[0:15]]
rest = max(0, len(elements) - 15)
return users, servers, rest
def format_netsplit(quit, msg):
users, (srv_left, srv_right), rest = parse_netsplit(msg.message)
affected = ', '.join(user.nick for user in users) + (f' ({rest} more)' if rest else '')
if quit:
return f'<= Netsplit between {srv_left} and {srv_right}. Users quit: {affected}'
return f'=> Netsplit between {srv_left} and {srv_right} ended. Users joined: {affected}'
def fmt(string):
return partial(lambda string, msg: string.format(**msg._asdict()), string)
FORMATTERS = {
MessageType.PRIVMSG: fmt('<{user}> {message}'),
MessageType.NOTICE: fmt('[{user}] {message}'),
MessageType.ACTION: fmt('-*- {user} {message}'),
MessageType.NICK: fmt('<-> {user} is now known as {message}'),
MessageType.MODE: fmt('*** Mode {message} by {user}'),
MessageType.JOIN: fmt('--> {user} ({user.host}) has joined {buffer}'),
MessageType.PART: format_part,
MessageType.QUIT: format_quit,
MessageType.KICK: format_kick,
MessageType.KILL: format_kill,
MessageType.SERVER: format_generic,
MessageType.INFO: format_generic,
MessageType.ERROR: format_generic,
MessageType.DAYCHANGE: format_generic,
MessageType.TOPIC: format_generic,
MessageType.NETSPLIT_JOIN: partial(format_netsplit, False),
MessageType.NETSPLIT_QUIT: partial(format_netsplit, True),
MessageType.INVITE: format_generic,
}
|