omlish 0.0.0.dev222__py3-none-any.whl → 0.0.0.dev223__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. omlish/__about__.py +4 -4
  2. omlish/asyncs/asyncio/subprocesses.py +15 -23
  3. omlish/dynamic.py +1 -1
  4. omlish/funcs/genmachine.py +1 -1
  5. omlish/http/coro/fdio.py +3 -3
  6. omlish/http/coro/server.py +1 -1
  7. omlish/http/handlers.py +14 -0
  8. omlish/http/parsing.py +13 -0
  9. omlish/lite/marshal.py +22 -6
  10. omlish/specs/irc/messages/__init__.py +0 -0
  11. omlish/specs/irc/messages/base.py +49 -0
  12. omlish/specs/irc/messages/formats.py +92 -0
  13. omlish/specs/irc/messages/messages.py +774 -0
  14. omlish/specs/irc/messages/parsing.py +98 -0
  15. omlish/specs/irc/numerics/numerics.py +57 -0
  16. omlish/subprocesses.py +88 -0
  17. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/METADATA +5 -5
  18. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/RECORD +32 -27
  19. /omlish/specs/irc/{format → protocol}/LICENSE +0 -0
  20. /omlish/specs/irc/{format → protocol}/__init__.py +0 -0
  21. /omlish/specs/irc/{format → protocol}/consts.py +0 -0
  22. /omlish/specs/irc/{format → protocol}/errors.py +0 -0
  23. /omlish/specs/irc/{format → protocol}/message.py +0 -0
  24. /omlish/specs/irc/{format → protocol}/nuh.py +0 -0
  25. /omlish/specs/irc/{format → protocol}/parsing.py +0 -0
  26. /omlish/specs/irc/{format → protocol}/rendering.py +0 -0
  27. /omlish/specs/irc/{format → protocol}/tags.py +0 -0
  28. /omlish/specs/irc/{format → protocol}/utils.py +0 -0
  29. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/LICENSE +0 -0
  30. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/WHEEL +0 -0
  31. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/entry_points.txt +0 -0
  32. {omlish-0.0.0.dev222.dist-info → omlish-0.0.0.dev223.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,774 @@
1
+ """
2
+ TODO:
3
+ - PONG - optional *before* required ...
4
+ - CAPABILITIES - ':' last param
5
+
6
+ ==
7
+
8
+ https://datatracker.ietf.org/doc/html/rfc2812
9
+ https://modern.ircdocs.horse/
10
+ """
11
+ import typing as ta
12
+
13
+ from .... import check
14
+ from .... import dataclasses as dc
15
+ from ..numerics import numerics as nr
16
+ from .base import Message
17
+ from .base import MessageFormat
18
+ from .base import list_pair_params_unpacker
19
+
20
+
21
+ #
22
+
23
+
24
+ _REGISTERED_IRC_MESSAGES_BY_NAME: dict[str, list[type['Message']]] = {}
25
+
26
+
27
+ def _register_irc_message(cls):
28
+ check.issubclass(cls, Message)
29
+ _REGISTERED_IRC_MESSAGES_BY_NAME.setdefault(cls.FORMAT.name, []).append(cls)
30
+ return cls
31
+
32
+
33
+ ##
34
+ # Connection Messages
35
+
36
+
37
+ @_register_irc_message
38
+ class CapMessage(Message):
39
+ FORMAT = MessageFormat.of(
40
+ 'CAP',
41
+ 'subcommand',
42
+ 'capabilities',
43
+ )
44
+
45
+ subcommand: str
46
+ capabilities: ta.Sequence[str]
47
+
48
+
49
+ @_register_irc_message
50
+ class AuthenticateMessage(Message):
51
+ FORMAT = MessageFormat.of('AUTHENTICATE')
52
+
53
+
54
+ @_register_irc_message
55
+ class PassMessage(Message):
56
+ FORMAT = MessageFormat.of(
57
+ 'PASS',
58
+ 'password',
59
+ )
60
+
61
+ REPLIES = (
62
+ nr.ERR_NEEDMOREPARAMS,
63
+ nr.ERR_ALREADYREGISTERED,
64
+ nr.ERR_PASSWDMISMATCH,
65
+ )
66
+
67
+ password: str
68
+
69
+
70
+ @_register_irc_message
71
+ class NickMessage(Message):
72
+ FORMAT = MessageFormat.of(
73
+ 'NICK',
74
+ 'nickname',
75
+ )
76
+
77
+ REPLIES = (
78
+ nr.ERR_NONICKNAMEGIVEN,
79
+ nr.ERR_ERRONEUSNICKNAME,
80
+ nr.ERR_NICKNAMEINUSE,
81
+ nr.ERR_NICKCOLLISION,
82
+ )
83
+
84
+ nickname: str
85
+
86
+
87
+ class UserMessage(Message):
88
+ FORMAT = MessageFormat.of(
89
+ 'USER',
90
+ 'username',
91
+ 'param1', # 0
92
+ 'param2', # *
93
+ 'realname',
94
+ )
95
+
96
+ REPLIES = (
97
+ nr.ERR_NEEDMOREPARAMS,
98
+ nr.ERR_ALREADYREGISTERED,
99
+ )
100
+
101
+ username: str
102
+ realname: str
103
+
104
+ param1: str = '8'
105
+ param2: str = '*'
106
+
107
+
108
+ @_register_irc_message
109
+ class PingMessage(Message):
110
+ FORMAT = MessageFormat.of(
111
+ 'PING',
112
+ 'token',
113
+ )
114
+
115
+ REPLIES = (
116
+ nr.ERR_NEEDMOREPARAMS,
117
+ nr.ERR_NOORIGIN,
118
+ nr.ERR_NOSUCHSERVER,
119
+ )
120
+
121
+ token: str
122
+
123
+
124
+ @_register_irc_message
125
+ class PongMessage(Message):
126
+ FORMAT = MessageFormat.of(
127
+ 'PONG',
128
+ '?server',
129
+ 'token',
130
+ )
131
+
132
+ token: str
133
+ server: str | None = None
134
+
135
+
136
+ @_register_irc_message
137
+ class OperMessage(Message):
138
+ FORMAT = MessageFormat.of(
139
+ 'OPER',
140
+ 'name',
141
+ 'password',
142
+ )
143
+
144
+ REPLIES = (
145
+ nr.ERR_NEEDMOREPARAMS,
146
+ nr.ERR_PASSWDMISMATCH,
147
+ nr.ERR_NOOPERHOST,
148
+ nr.RPL_YOUREOPER,
149
+ )
150
+
151
+ name: str
152
+ password: str
153
+
154
+
155
+ @_register_irc_message
156
+ class QuitMessage(Message):
157
+ FORMAT = MessageFormat.of(
158
+ 'QUIT',
159
+ '?reason',
160
+ )
161
+
162
+ reason: str | None = None
163
+
164
+
165
+ @_register_irc_message
166
+ class ErrorMessage(Message):
167
+ FORMAT = MessageFormat.of(
168
+ 'ERROR',
169
+ '?reason',
170
+ )
171
+
172
+ reason: str | None = None
173
+
174
+
175
+ ##
176
+ # Channel Operations
177
+
178
+
179
+ @_register_irc_message
180
+ class JoinMessage(Message):
181
+ FORMAT = MessageFormat.of(
182
+ 'JOIN',
183
+ ','
184
+ 'channels', '?,keys',
185
+ unpack_params=list_pair_params_unpacker(
186
+ 'channels',
187
+ 'channels',
188
+ 'keys',
189
+ ),
190
+ )
191
+
192
+ REPLIES = (
193
+ nr.ERR_NEEDMOREPARAMS,
194
+ nr.ERR_NOSUCHCHANNEL,
195
+ nr.ERR_TOOMANYCHANNELS,
196
+ nr.ERR_BADCHANNELKEY,
197
+ nr.ERR_BANNEDFROMCHAN,
198
+ nr.ERR_CHANNELISFULL,
199
+ nr.ERR_INVITEONLYCHAN,
200
+ nr.ERR_BADCHANMASK,
201
+ nr.RPL_TOPIC,
202
+ nr.RPL_TOPICWHOTIME,
203
+ nr.RPL_NAMREPLY,
204
+ nr.RPL_ENDOFNAMES,
205
+ )
206
+
207
+ channels: ta.Sequence[str | tuple[str, str]]
208
+
209
+ @property
210
+ def has_keys(self) -> bool:
211
+ return check.single({isinstance(e, tuple) for e in self.channels})
212
+
213
+ @dc.init
214
+ def _validate_channels(self) -> None:
215
+ check.not_isinstance(self.channels, str)
216
+ if self.has_keys:
217
+ for c, _ in self.channels: # type: ignore[misc]
218
+ check.non_empty_str(c)
219
+ else:
220
+ for c in self.channels:
221
+ check.non_empty_str(c) # type: ignore
222
+
223
+
224
+ @_register_irc_message
225
+ class LeaveAllJoinMessage(Message):
226
+ FORMAT = MessageFormat.of(
227
+ 'JOIN',
228
+ MessageFormat.LiteralParam('0'),
229
+ )
230
+
231
+ REPLIES = JoinMessage.REPLIES
232
+
233
+
234
+ @_register_irc_message
235
+ class PartMessage(Message):
236
+ FORMAT = MessageFormat.of(
237
+ 'PART',
238
+ ',channels',
239
+ '?reason',
240
+ )
241
+
242
+ REPLIES = (
243
+ nr.ERR_NEEDMOREPARAMS,
244
+ nr.ERR_NOSUCHCHANNEL,
245
+ nr.ERR_NOTONCHANNEL,
246
+ )
247
+
248
+ channels: ta.Sequence[str] = dc.xfield(validate=check.of_not_isinstance(str))
249
+ reason: str | None = None
250
+
251
+
252
+ @_register_irc_message
253
+ class TopicMessage(Message):
254
+ FORMAT = MessageFormat.of(
255
+ 'TOPIC',
256
+ 'channel',
257
+ '?topic',
258
+ )
259
+
260
+ REPLIES = (
261
+ nr.ERR_NEEDMOREPARAMS,
262
+ nr.ERR_NOSUCHCHANNEL,
263
+ nr.ERR_NOTONCHANNEL,
264
+ nr.ERR_CHANOPRIVSNEEDED,
265
+ nr.RPL_NOTOPIC,
266
+ nr.RPL_TOPIC,
267
+ nr.RPL_TOPICWHOTIME,
268
+ )
269
+
270
+ channel: str
271
+ topic: str | None = None
272
+
273
+
274
+ @_register_irc_message
275
+ class NamesMessage(Message):
276
+ FORMAT = MessageFormat.of(
277
+ 'NAMES',
278
+ ',channels',
279
+ )
280
+
281
+ REPLIES = (
282
+ nr.RPL_NAMREPLY,
283
+ nr.RPL_ENDOFNAMES,
284
+ )
285
+
286
+ channels: ta.Sequence[str] = dc.xfield(validate=check.of_not_isinstance(str))
287
+
288
+
289
+ @_register_irc_message
290
+ class ListMessage(Message):
291
+ FORMAT = MessageFormat.of(
292
+ 'LIST',
293
+ '?,channels',
294
+ '?,elistconds',
295
+ )
296
+
297
+ REPLIES = (
298
+ nr.RPL_LISTSTART,
299
+ nr.RPL_LIST,
300
+ nr.RPL_LISTEND,
301
+ )
302
+
303
+ channels: ta.Sequence[str]
304
+ elistconds: ta.Sequence[str]
305
+
306
+
307
+ @_register_irc_message
308
+ class InviteMessage(Message):
309
+ FORMAT = MessageFormat.of(
310
+ 'INVITE',
311
+ 'nickname',
312
+ 'channel',
313
+ )
314
+
315
+ REPLIES = (
316
+ nr.RPL_INVITING,
317
+ nr.ERR_NEEDMOREPARAMS,
318
+ nr.ERR_NOSUCHCHANNEL,
319
+ nr.ERR_NOTONCHANNEL,
320
+ nr.ERR_CHANOPRIVSNEEDED,
321
+ nr.ERR_USERONCHANNEL,
322
+ )
323
+
324
+ nickname: str
325
+ channel: str
326
+
327
+
328
+ @_register_irc_message
329
+ class KickMessage(Message):
330
+ FORMAT = MessageFormat.of(
331
+ 'KICK',
332
+ 'channel',
333
+ ',users',
334
+ '?comment',
335
+ )
336
+
337
+ REPLIES = (
338
+ nr.ERR_NEEDMOREPARAMS,
339
+ nr.ERR_NOSUCHCHANNEL,
340
+ nr.ERR_CHANOPRIVSNEEDED,
341
+ nr.ERR_USERNOTINCHANNEL,
342
+ nr.ERR_NOTONCHANNEL,
343
+ nr.ERR_BADCHANMASK,
344
+ )
345
+
346
+ channel: str
347
+ users: ta.Sequence[str]
348
+ comment: str | None = None
349
+
350
+
351
+ ##
352
+ # Server Queries and Commands
353
+
354
+
355
+ @_register_irc_message
356
+ class MotdMessage(Message):
357
+ FORMAT = MessageFormat.of(
358
+ 'MOTD',
359
+ '?target',
360
+ )
361
+
362
+ REPLIES = (
363
+ nr.ERR_NOSUCHSERVER,
364
+ nr.ERR_NOMOTD,
365
+ nr.RPL_MOTDSTART,
366
+ nr.RPL_MOTD,
367
+ nr.RPL_ENDOFMOTD,
368
+ )
369
+
370
+ target: str | None = None
371
+
372
+
373
+ @_register_irc_message
374
+ class VersionMessage(Message):
375
+ FORMAT = MessageFormat.of(
376
+ 'VERSION',
377
+ '?target',
378
+ )
379
+
380
+ REPLIES = (
381
+ nr.ERR_NOSUCHSERVER,
382
+ nr.RPL_ISUPPORT,
383
+ nr.RPL_VERSION,
384
+ )
385
+
386
+ target: str | None = None
387
+
388
+
389
+ @_register_irc_message
390
+ class AdminMessage(Message):
391
+ FORMAT = MessageFormat.of(
392
+ 'ADMIN',
393
+ '?target',
394
+ )
395
+
396
+ REPLIES = (
397
+ nr.ERR_NOSUCHSERVER,
398
+ nr.RPL_ADMINME,
399
+ nr.RPL_ADMINLOC1,
400
+ nr.RPL_ADMINLOC2,
401
+ nr.RPL_ADMINEMAIL,
402
+ )
403
+
404
+ target: str | None = None
405
+
406
+
407
+ @_register_irc_message
408
+ class ConnectMessage(Message):
409
+ FORMAT = MessageFormat.of(
410
+ 'CONNECT',
411
+ 'target_server',
412
+ '?port',
413
+ '?remote_server',
414
+ )
415
+
416
+ REPLIES = (
417
+ nr.ERR_NOSUCHSERVER,
418
+ nr.ERR_NEEDMOREPARAMS,
419
+ nr.ERR_NOPRIVILEGES,
420
+ nr.ERR_NOPRIVS,
421
+ )
422
+
423
+ target_server: str
424
+ port: str | None = None
425
+ remote_server: str | None = None
426
+
427
+
428
+ @_register_irc_message
429
+ class LusersMessage(Message):
430
+ FORMAT = MessageFormat.of('LUSERS')
431
+
432
+ REPLIES = (
433
+ nr.RPL_LUSERCLIENT,
434
+ nr.RPL_LUSEROP,
435
+ nr.RPL_LUSERUNKNOWN,
436
+ nr.RPL_LUSERCHANNELS,
437
+ nr.RPL_LUSERME,
438
+ nr.RPL_LOCALUSERS,
439
+ nr.RPL_GLOBALUSERS,
440
+ )
441
+
442
+
443
+ @_register_irc_message
444
+ class TimeMessage(Message):
445
+ FORMAT = MessageFormat.of(
446
+ 'TIME',
447
+ '?server',
448
+ )
449
+
450
+ REPLIES = (
451
+ nr.ERR_NOSUCHSERVER,
452
+ nr.RPL_TIME,
453
+ )
454
+
455
+ server: str | None = None
456
+
457
+
458
+ @_register_irc_message
459
+ class StatsMessage(Message):
460
+ FORMAT = MessageFormat.of(
461
+ 'STATS',
462
+ 'query',
463
+ '?server',
464
+ )
465
+
466
+ REPLIES = (
467
+ nr.ERR_NOSUCHSERVER,
468
+ nr.ERR_NEEDMOREPARAMS,
469
+ nr.ERR_NOPRIVILEGES,
470
+ nr.ERR_NOPRIVS,
471
+ nr.RPL_STATSCLINE,
472
+ nr.RPL_STATSHLINE,
473
+ nr.RPL_STATSILINE,
474
+ nr.RPL_STATSKLINE,
475
+ nr.RPL_STATSLLINE,
476
+ nr.RPL_STATSOLINE,
477
+ nr.RPL_STATSLINKINFO,
478
+ nr.RPL_STATSUPTIME,
479
+ nr.RPL_STATSCOMMANDS,
480
+ nr.RPL_ENDOFSTATS,
481
+ )
482
+
483
+ query: str
484
+ server: str | None = None
485
+
486
+
487
+ @_register_irc_message
488
+ class HelpMessage(Message):
489
+ FORMAT = MessageFormat.of(
490
+ 'HELP',
491
+ '?subject',
492
+ )
493
+
494
+ REPLIES = (
495
+ nr.ERR_HELPNOTFOUND,
496
+ nr.RPL_HELPSTART,
497
+ nr.RPL_HELPTXT,
498
+ nr.RPL_ENDOFHELP,
499
+ )
500
+
501
+ subject: str | None = None
502
+
503
+
504
+ @_register_irc_message
505
+ class InfoMessage(Message):
506
+ FORMAT = MessageFormat.of('INFO')
507
+
508
+ REPLIES = (
509
+ nr.RPL_INFO,
510
+ nr.RPL_ENDOFINFO,
511
+ )
512
+
513
+
514
+ @_register_irc_message
515
+ class ModeMessage(Message):
516
+ FORMAT = MessageFormat.of(
517
+ 'MODE',
518
+ 'target',
519
+ '?modestring',
520
+ '*mode_arguments',
521
+ )
522
+
523
+ REPLIES = (
524
+ nr.ERR_NOSUCHCHANNEL,
525
+ nr.RPL_CHANNELMODEIS,
526
+ nr.RPL_CREATIONTIME,
527
+ nr.RPL_CHANNELMODEIS,
528
+ nr.ERR_CHANOPRIVSNEEDED,
529
+ nr.RPL_BANLIST,
530
+ nr.RPL_ENDOFBANLIST,
531
+ nr.RPL_EXCEPTLIST,
532
+ nr.RPL_ENDOFEXCEPTLIST,
533
+ nr.RPL_INVITELIST,
534
+ nr.RPL_ENDOFINVITELIST,
535
+ )
536
+
537
+ target: str
538
+ modestring: str | None = None
539
+ mode_arguments: ta.Sequence[str] | None = None
540
+
541
+
542
+ ##
543
+ # Sending Messages
544
+
545
+
546
+ @_register_irc_message
547
+ class PrivmsgMessage(Message):
548
+ FORMAT = MessageFormat.of(
549
+ 'PRIVMSG',
550
+ ',targets',
551
+ 'text',
552
+ )
553
+
554
+ REPLIES = (
555
+ nr.ERR_NOSUCHNICK,
556
+ nr.ERR_NOSUCHSERVER,
557
+ nr.ERR_CANNOTSENDTOCHAN,
558
+ nr.ERR_TOOMANYTARGETS,
559
+ nr.ERR_NORECIPIENT,
560
+ nr.ERR_NOTEXTTOSEND,
561
+ nr.ERR_NOTOPLEVEL,
562
+ nr.ERR_WILDTOPLEVEL,
563
+ nr.RPL_AWAY,
564
+ )
565
+
566
+ targets: ta.Sequence[str]
567
+ text: str
568
+
569
+
570
+ @_register_irc_message
571
+ class NoticeMessage(Message):
572
+ FORMAT = MessageFormat.of(
573
+ 'NOTICE',
574
+ ',targets',
575
+ 'text',
576
+ )
577
+
578
+ targets: ta.Sequence[str]
579
+ text: str
580
+
581
+
582
+ ##
583
+ # User-Based Queries
584
+
585
+ @_register_irc_message
586
+ class WhoMessage(Message):
587
+ FORMAT = MessageFormat.of(
588
+ 'WHO',
589
+ 'mask',
590
+ )
591
+
592
+ REPLIES = (
593
+ nr.RPL_WHOREPLY,
594
+ nr.RPL_ENDOFWHO,
595
+ )
596
+
597
+ mask: str
598
+
599
+
600
+ @_register_irc_message
601
+ class WhoisMessage(Message):
602
+ FORMAT = MessageFormat.of(
603
+ 'WHOIS',
604
+ '?target',
605
+ 'nick',
606
+ )
607
+
608
+ REPLIES = (
609
+ nr.ERR_NOSUCHNICK,
610
+ nr.ERR_NOSUCHSERVER,
611
+ nr.ERR_NONICKNAMEGIVEN,
612
+ nr.RPL_WHOISCERTFP,
613
+ nr.RPL_WHOISREGNICK,
614
+ nr.RPL_WHOISUSER,
615
+ nr.RPL_WHOISSERVER,
616
+ nr.RPL_WHOISOPERATOR,
617
+ nr.RPL_WHOISIDLE,
618
+ nr.RPL_WHOISCHANNELS,
619
+ nr.RPL_WHOISSPECIAL,
620
+ nr.RPL_WHOISACCOUNT,
621
+ nr.RPL_WHOISACTUALLY,
622
+ nr.RPL_WHOISHOST,
623
+ nr.RPL_WHOISMODES,
624
+ nr.RPL_WHOISSECURE,
625
+ nr.RPL_AWAY,
626
+ )
627
+
628
+ nick: str
629
+ target: str | None = None
630
+
631
+
632
+ @_register_irc_message
633
+ class WhowasMessage(Message):
634
+ FORMAT = MessageFormat.of(
635
+ 'WHOWAS',
636
+ 'nick',
637
+ '?count',
638
+ )
639
+
640
+ REPLIES = (
641
+ nr.ERR_WASNOSUCHNICK,
642
+ nr.RPL_ENDOFWHOWAS,
643
+ nr.RPL_WHOWASUSER,
644
+ nr.RPL_WHOISACTUALLY,
645
+ nr.RPL_WHOISSERVER,
646
+ nr.RPL_WHOWASUSER,
647
+ nr.ERR_NONICKNAMEGIVEN,
648
+ nr.ERR_NEEDMOREPARAMS,
649
+ )
650
+
651
+ nick: str
652
+ count: str | None = None
653
+
654
+
655
+ ##
656
+ # Operator Messages
657
+
658
+
659
+ @_register_irc_message
660
+ class KillMessage(Message):
661
+ FORMAT = MessageFormat.of(
662
+ 'KILL',
663
+ 'nickname',
664
+ 'comment',
665
+ )
666
+
667
+ REPLIES = (
668
+ nr.ERR_NOSUCHSERVER,
669
+ nr.ERR_NEEDMOREPARAMS,
670
+ nr.ERR_NOPRIVILEGES,
671
+ nr.ERR_NOPRIVS,
672
+ )
673
+
674
+ nickname: str
675
+ comment: str
676
+
677
+
678
+ @_register_irc_message
679
+ class RehashMessage(Message):
680
+ FORMAT = MessageFormat.of('REHASH')
681
+
682
+ REPLIES = (
683
+ nr.RPL_REHASHING,
684
+ nr.ERR_NOPRIVILEGES,
685
+ )
686
+
687
+
688
+ @_register_irc_message
689
+ class RestartMessage(Message):
690
+ FORMAT = MessageFormat.of('RESTART')
691
+
692
+ REPLIES = (
693
+ nr.ERR_NOPRIVILEGES,
694
+ )
695
+
696
+
697
+ @_register_irc_message
698
+ class SquitMessage(Message):
699
+ FORMAT = MessageFormat.of(
700
+ 'SQUIT',
701
+ 'server',
702
+ 'comment',
703
+ )
704
+
705
+ REPLIES = (
706
+ nr.ERR_NOSUCHSERVER,
707
+ nr.ERR_NEEDMOREPARAMS,
708
+ nr.ERR_NOPRIVILEGES,
709
+ nr.ERR_NOPRIVS,
710
+ )
711
+
712
+ server: str
713
+ comment: str
714
+
715
+
716
+ ##
717
+ # Optional Messages
718
+
719
+
720
+ @_register_irc_message
721
+ class AwayMessage(Message):
722
+ FORMAT = MessageFormat.of(
723
+ 'AWAY',
724
+ '?text',
725
+ )
726
+
727
+ REPLIES = (
728
+ nr.RPL_AWAY,
729
+ nr.RPL_UNAWAY,
730
+ nr.RPL_NOWAWAY,
731
+ )
732
+
733
+ text: str | None = None
734
+
735
+
736
+ @_register_irc_message
737
+ class LinksMessage(Message):
738
+ FORMAT = MessageFormat.of('LINKS')
739
+
740
+ REPLIES = (
741
+ nr.RPL_LINKS,
742
+ nr.RPL_ENDOFLINKS,
743
+ )
744
+
745
+
746
+ @_register_irc_message
747
+ class UserhostMessage(Message):
748
+ FORMAT = MessageFormat.of(
749
+ 'USERHOST',
750
+ '*nicknames',
751
+ )
752
+
753
+ REPLIES = (
754
+ nr.ERR_NEEDMOREPARAMS,
755
+ nr.RPL_USERHOST,
756
+ )
757
+
758
+ nicknames: ta.Sequence[str] | None = dc.xfield(default=None, validate=check.of_not_isinstance(str))
759
+
760
+
761
+ @_register_irc_message
762
+ class WallopsMessage(Message):
763
+ FORMAT = MessageFormat.of(
764
+ 'WALLOPS',
765
+ 'text',
766
+ )
767
+
768
+ REPLIES = (
769
+ nr.ERR_NEEDMOREPARAMS,
770
+ nr.ERR_NOPRIVILEGES,
771
+ nr.ERR_NOPRIVS,
772
+ )
773
+
774
+ text: str